数据结构之顺序表+常见面试题

数据结构分为两种

1、物理结构(内存中如何存储的)

2、逻辑结构(是我们想象出来的)

线性表(逻辑上连续)包括顺序表、链表、栈、队列。

顺序表在物理上和逻辑上都是连续的(其实就是数组)

静态顺序表的基本结构

struct static_Array_list
{
	int arr[10];
	int size;
};

存在的问题

1、数据类型无法改变

2、数组大小无法改变

所以引入动态顺序表。

动态顺序表的基本结构

typedef int SLDataType;
typedef struct dynamic_Array_list
{
	SLDataType *arr;//定义数组指针
	size_t size;//有效数据个数
	size_t capacity//容量
}ArrayList;

缺点

1、顺序表在尾插尾删上很方便,但是在头(中间)插头(中间)删上复杂度为O(n)

2、空间利用率不高,增容会有一定空间浪费(批量增容)

优点

1、内存连续,随机访问

2、缓存命中率高(因为内存连续,预加载时把一段数据加载到缓存中,如果内存不连续,则会进行多次加载来获取所需要的的数据)

顺序表的基本接口

// 基本增删查改接口
// 顺序表初始化
void ArrayListInit(ArrayList* psl, size_t capacity);
// 顺序表销毁
void ArrayListDestory(ArrayList* psl);
// 顺序表打印
void ArrayListPrint(ArrayList* psl);
// 检查空间,如果满了,进行增容
void CheckCapacity(ArrayList* psl);
// 顺序表尾插
void ArrayListPushBack(ArrayList* psl, SLDataType x);
// 顺序表尾删
void ArrayListPopBack(ArrayList* psl);
// 顺序表头插
void ArrayListPushFront(ArrayList* psl, SLDataType x);
// 顺序表头删
void ArrayListPopFront(ArrayList* psl);
// 顺序表查找
int ArrayListFind(ArrayList* psl, SLDataType x);
// 顺序表在pos位置插入x
void ArrayListInsert(ArrayList* psl, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void ArrayListErase(ArrayList* psl, size_t pos);

总的来说顺序表比较简单,因为底层是可以动态开辟空间的数组,基本接口的实现网上已经有很多相关的内容,不在赘述。

顺序表常见的面试题

1. 原地移除数组中所有的元素val,要求时间复杂度为O(N),空间复杂度为O(1)。

//为了达到O(1)的空间复杂度(不能额外开辟数组长度大小的空间)
//时间复杂度O(N)(只能遍历一次数组)
//所以采用双指针的方法,用不重复的元素替换掉重复的元素
//注意各种判断条件
int removeElement(int* nums, int numsSize, int val){
    int head = 0;
    int end = numsSize-1;
    while(head<=end)//加=号的目的是防止最后双指针相遇,相遇点正好是要删除的数值
    {
        while(head<numsSize&&nums[head]!=val)
        {
            head++;
        }
        while(end>=0&&nums[end]==val)
        {
            end--;
        }
        if(head<end)
            {
                nums[head]=nums[end];
                head++;
                end--;
            }
    }
    return end+1;
}

2. 删除排序数组中的重复项。

//给排序数组元素去重
//同样的,空间复杂度要求是O(1)
//还是利用双指针的思想,从前往后替换
//注意数组为空的情况
int removeDuplicates(int* nums, int numsSize){

    if(numsSize==0)
        return 0;
    int head1 = 0;
    int head2 = 1;
    while(head2<=numsSize-1)
    {
        while(head2<=numsSize-1&&nums[head1] == nums[head2])
        {
            head2++;
        }
        if(head2<=numsSize-1)
        {
            nums[++head1] = nums[head2];
        }
    }
    return head1+1;
}

3. 合并两个有序数组。

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){

    //开辟额外的空间
    int *tmp = (int*)malloc(sizeof(int)*(m+n));
    //三个索引
    int index1=0;
    int index2=0;
    int index=0;
    while(index1<m && index2<n)
    {
        if(nums1[index1]<nums2[index2])
        {
            tmp[index++]=nums1[index1];
            index1++;
        }
        else
        {
            tmp[index++]=nums2[index2];
            index2++;
        }
    }
//下面是将尾部的元素拷贝过来,可以优化,采用尾指针的方式
//自动判断谁的尾部还有剩下的元素
    for(int i=index1;i<m;i++)
        tmp[index++]=nums1[i];
    for(int i=index2;i<n;i++)
        tmp[index++]=nums2[i];
//拷贝给nums1
    for(int i=0;i<index;i++)
        nums1[i]=tmp[i];
//整体来说时间复杂度O(2n+m),空间复杂度O(n+m)//可以优化
}

4.旋转数组。

//方法1.新建一个数组
//该方法比较简单,但空间复杂度高O(N)
void rotate(int* nums, int numsSize, int k){

    int* array_tmp = (int*)malloc(sizeof(int)*numsSize);
    k=k%numsSize;//值得注意的地方
    for(int i=0;i<k;i++)
    {
        array_tmp[i]=nums[numsSize-k+i];
    }
    for(int i = k;i<numsSize;i++)
    {
        array_tmp[i]=nums[i-k];
    }
    for(int i = 0;i<numsSize;i++)
    {
        nums[i] = array_tmp[i];
    }
}

//方法2.旋转数组
//时间复杂度O(N),空间复杂度O(1)
void Reverse(int* nums, int numsSize)
{
    for(int i = 0; i < numsSize/2; i++)
    {
        int tmp = nums[i];
        nums[i] = nums[numsSize-i-1];
        nums[numsSize-i-1] = tmp;
    }
}
void rotate(int* nums, int numsSize, int k){

    k = k%numsSize;
    if(k!=0)//如果k==0,说明原来数组的没变
    {
        Reverse(nums,numsSize);
        Reverse(nums,k);
        Reverse(nums+k,numsSize-k);
    }
}

5. 数组形式的整数加法。

//逐位相加,然后逆转数组
int* addToArrayForm(int* num, int numSize, int k, int* returnSize){

    *returnSize = 0;
    int* returnArray = (int*)calloc(fmax(10, numSize + 1),sizeof(int));
    while(k || numSize)
    {
        if(numSize)
        {
            returnArray[*returnSize] = returnArray[*returnSize] + num[numSize-1] + k%10;
            numSize--;
        }
        else
        {
            returnArray[*returnSize] = returnArray[*returnSize] + k%10;
        }
        if(returnArray[*returnSize] > 9)
        {
            returnArray[*returnSize] = returnArray[*returnSize]%10;
            returnArray[(*returnSize) + 1] = 1;
            if(!(k/10)&&!numSize)//防止最后一个数发生进位,数据的长度会增加
                (*returnSize)++;
        }
       
        k = k/10;
        (*returnSize)++;
    }
    for (int i = 0; i < (*returnSize) / 2; i++)
    {
        int tmp = returnArray[i];
        returnArray[i] = returnArray[(*returnSize) - 1 - i];
        returnArray[(*returnSize) - 1 - i] = tmp;
    }
    return returnArray;
}

总的来说顺序表是非常基础的一种数据结构,用处也非常广泛,在后续的数据结构中也有很多衍生出来的结构是基于顺序表的。与之相反的一种数据结构是链表,链表和顺序表的特性恰恰相反,下篇文章共同学习线性表中的链表。

    原文作者:TangguTae
    原文地址: https://blog.csdn.net/weixin_43164548/article/details/120785605
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞