【数据结构】手撕顺序表

目录

一、线性表介绍

二、顺序表

2.1.概念

2.2.结构可视化

(1)顺序表的静态存储

(2)顺序表的动态存储

2.3 接口实现——增删查改

(1)顺序表初始化

(2)顺序表的销毁

(3)检查和开辟空间

(4)头部插入

(5)尾部插入

(6)头部删除

(7)尾部删除

(8)查找数据

(9)非特殊情况,直接插入

(10)非特殊情况,直接删除

(11)修改数据

三、总结

1.顺序表优点:

2.顺序表缺点:


一、线性表介绍

         线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...

【数据结构】手撕顺序表_第1张图片

         虽然它在逻辑上是线性结构,也就是连续的一条直线;在物理结构上不一定是连续的,通常以数组和链式结构形式存储。


二、顺序表

2.1.概念

        顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

2.2.结构可视化

(1)顺序表的静态存储

【数据结构】手撕顺序表_第2张图片

考虑静态顺序表的缺点:

       仅适用于确定存储多少数据的场景,开辟空间不够灵活。

       空间开多了浪费,开少了需频繁扩容,操作麻烦。

(2)顺序表的动态存储

【数据结构】手撕顺序表_第3张图片

2.3 接口实现——增删查改

(1)顺序表初始化

void SLInit(SL* psl)
{
       assert(psl);
       psl->a = NULL;
       psl->capacity = psl->size = 0;
}

(2)顺序表的销毁

void SLDestory(SL* psl)
{
       assert(psl);
       /*if (psl->a)
       {*/
       free(psl->a);
       psl->a = NULL;
       psl->capacity = psl->size = 0;
       //}
}

(3)检查和开辟空间

void SLCheckCapacity(SL* psl)
{
       // 检查容量
       if (psl->size == psl->capacity)
       {
              int newCapcity = psl->capacity == 0 ? 4 : psl->capacity * 2;
              SLDataType* tmp = (SLDataType*)realloc(psl->a, newCapcity *  sizeof(SLDataType));
              if (tmp == NULL)
              {
                      perror("realloc fail");
                      return;
                      //exit(-1);
              }
              psl->a = tmp;
              psl->capacity = newCapcity;
       }
}

注:以下代码块均用两种方法实现。

一种是普普通通的挪动数据,进行插入、删除(被注释//掉了)

一种是另写了SLInsert函数和SLErase函数进行插入、删除(见9、10)

(4)头部插入

void SLPushFront(SL* psl, SLDataType x)
{
       //assert(psl);
       //SLCheckCapacity(psl);
        挪动数据
       //int end = psl->size - 1;
       //while (end >= 0)
       //{
       //     psl->a[end + 1] = psl->a[end];
       //     --end;
       //}
       //psl->a[0] = x;
       //psl->size++;
       SLInsert(psl, 0, x);
}

(5)尾部插入

void SLPushBack(SL* psl, SLDataType x)
{
       /*assert(psl);
       SLCheckCapacity(psl);
       psl->a[psl->size] = x;
       psl->size++;*/
       SLInsert(psl, psl->size, x);
}

(6)头部删除

void SLPopFront(SL* psl)
{
       assert(psl);
       assert(psl->size > 0);
       /*int begin = 0;
       while (begin < psl->size - 1)
       {
              psl->a[begin] = psl->a[begin + 1];
              ++begin;
       }*/
       int begin = 1;
       while (begin < psl->size)
       {
              psl->a[begin-1] = psl->a[begin];
              ++begin;
       }
       --psl->size;//前置后置都可以
}

(7)尾部删除

void SLPopBack(SL* psl)
{
       assert(psl);
       // 温柔的检查
       /*if (psl->size == 0)
       {
           return;
       }*/
       // 暴力的检查
       assert(psl->size > 0);
       psl->size--;
}

(8)查找数据

int SLFind(SL* psl, SLDataType x)
{
       assert(psl);
       for (int i = 0; i < psl->size; i++)
       {
              if (psl->a[i] == x)
              {
                      return i;//返回的是下标
              }
       }
       return -1;
}

(9)非特殊情况,直接插入

void SLInsert(SL* psl, size_t pos, SLDataType x)
{
       assert(psl);
       assert(pos <= psl->size);//等于的时候就相当于尾插
       SLCheckCapacity(psl);
       //挪动数据
       size_t end = psl->size;//先确定一个end,如果用size_t到时候,pos为0时,end负数就会变成一个很大的数,导致进入死循环
       while (end > pos)
       {
              psl->a[end] = psl->a[end-1];
              --end;
       }
       psl->a[pos] = x;
       ++psl->size;
}

(10)非特殊情况,直接删除

void SLErase(SL* psl, size_t pos)
{
       assert(psl);
       assert(pos <= psl->size);//可以等于的原因是相当于尾插
       size_t begin = pos;
       while (begin < psl->size - 1)
       {
              psl->a[begin] = psl->a[begin + 1];
              ++begin;
       }
       psl->size--;
}

(11)修改数据

void SLModify(SL* psl, size_t pos, SLDataType x)
{
       assert(psl);
       assert(pos < psl->size);
       psl->a[pos] = x;
}

三、总结

1.顺序表优点:

(1)尾插尾删效率很高

(2)随机访问(用下标访问)

(3)相比链表结构,CPU高速缓存命中率更高

2.顺序表缺点:

(1)头部和中部插入删除效率低——O(N)

(2)扩容时单次性能消耗大。一次扩多了,存在空间浪费;扩少了,频繁扩容,效率损失


:如果对您有帮助的话,不要忘记一键三连哦,撒花

你可能感兴趣的:(数据结构,链表)