主要参考:
【王道考研】王道数据结构与算法详细笔记(全)_王道数据结构笔记-CSDN博客
顺序表的概念
顺序表:用顺序存储的方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
顺序表的特点:
1. 随机访问,即可以在O(1)时间内找到第 i 个元素。
2. 存储密度高,每个节点只存储数据元素。
3. 拓展容量不方便(即使使用动态分配的方式实现,拓展长度的时间复杂度也比较高,因为需要把数据复制到新的区域)。
5. 插入删除操作不方便,需移动大量元素:O(n)
顺序表的实现
所谓顺序表,其实就是申请一整块的内存空间,比如我们可以定义一个数组或者malloc一片空间,然后我们再基于这块空间来进行数据项管理。
顺序表的静态分配 顺序表的表长刚开始确定后就无法更改(存储空间是静态的)
//顺序表的实现--静态分配 #include<stdio.h> #define MaxSize 10 //定义表的最大长度 typedef struct{ int data[MaxSize]; //用静态的"数组"存放数据元素 int length; //顺序表的当前长度 }SqList; //顺序表的类型定义(静态分配方式) void InitList(SqList &L){ for(int i=0;i<MaxSize;i++){ L.data[i]=0; //将所有数据元素设置为默认初始值 } L.length=0; } int main(){ SqList L; //声明一个顺序表 InitList(L); //初始化一个顺序表 for(int i=0;i<MaxSize;i++){ //顺序表的打印 printf("data[%d]=%d\n",i,L.data[i]); } return 0; }
顺序表的动态分配
//顺序表的实现——动态分配 #include<stdio.h> #include<stdlib.h> //malloc、free函数的头文件 #define InitSize 10 //默认的初始值 typedef struct{ int *data; //指示动态分配数组的指针 int MaxSize; //顺序表的最大容量 int length; //顺序表的当前长度 }SeqList; void InitList(SeqList &L){ //初始化 //用malloc 函数申请一片连续的存储空间 L.data =(int*)malloc(InitSize*sizeof(int)) ; L.length=0; L.MaxSize=InitSize; } void IncreaseSize(SeqList &L,int len){ //增加动态数组的长度 int *p=L.data; L.data=(int*)malloc((L.MaxSize+len)*sizeof(int)); for(int i=0;i<L.length;i++){ L.data[i]=p[i]; //将数据复制到新区域 } L.MaxSize=L.MaxSize+len; //顺序表最大长度增加len free(p); //释放原来的内存空间 } int main(){ SeqList L; //声明一个顺序表 InitList(L); //初始化顺序表 IncreaseSize(L,5);//增加顺序表的长度 return 0; }
注意,顺序表在定义时需要用结构体封装一个数组,用来存储元素,数组的大小就是该顺序表的最大容量,另外,需要封装一个长度,用来表示当前顺序表的有效长度,也就是当前所容纳的数据个数。
因为数组在声明时可以同时指定指针和容量,所以只需要定义两个变量;如果是使用动态分配的方式,就需要定义一个指针,一个最大容量,一个当前长度,共三个变量。但二者本质上是一样的。
顺序表的插入操作 ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e。 平均时间复杂度 = O(n)
#define MaxSize 10 //定义最大长度 typedef struct{ int data[MaxSize]; //用静态的数组存放数据 int length; //顺序表的当前长度 }SqList; //顺序表的类型定义 bool ListInsert(SqList &L, int i, int e){ if(i<1||i>L.length+1) //判断i的范围是否有效 return false; if(L.length>=MaxSize) //当前存储空间已满,不能插入 return false; for(int j=L.length; j>=i; j--){ //将第i个元素及其之后的元素后移 L.data[j]=L.data[j-1]; } L.data[i-1]=e; //在位置i处放入e L.length++; //长度加1 return true; } int main(){ SqList L; //声明一个顺序表 InitList(L);//初始化顺序表 //...此处省略一些代码;插入几个元素 ListInsert(L,3,3); //再顺序表L的第三行插入3 return 0; }
注意,这些数据结构,插入数据的时候必须是相连的,比如顺序表,插入的时候不能让连续的数据断开。
顺序表的删除操作 ListDelete(&Li,&e): 删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。 平均时间复杂度 = O(n)
#define MaxSize 10 typedef struct { int data[MaxSize]; int length; } SqList; // 删除顺序表i位置的数据并存入e bool ListDelete(SqList &L, int i, int &e) { if (i < 1 || i > L.length) // 判断i的范围是否有效 return false; e = L.data[i-1]; // 将被删除的元素赋值给e for (int j = i; j < L.length; j++) //将第i个位置后的元素前移 L.data[j-1] = L.data[j]; L.length--; return true; } int main() { SqList L; InitList(L); int e = -1; if (ListDelete(L, 3, e)) printf("已删除第3个元素,删除元素值为%d\n", e); else printf("位序i不合法,删除失败\n"); return 0; }
因为顺序表要保持连续性,所以插入和删除时都需要移动操作点之后的数据,这也就是时间复杂度所在之处。
顺序表的按位查找 GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值 平均时间复杂度O(1)
// 静态分配的按位查找 #define MaxSize 10 typedef struct { ElemType data[MaxSize]; int length; }SqList; ElemType GetElem(SqList L, int i) { return L.data[i-1]; }
// 动态分配的按位查找 #define InitSize 10 typedef struct { ElemType *data; int MaxSize; int length; }SeqList; ElemType GetElem(SeqList L, int i) { return L.data[i-1]; }
按位查找的应用之一就是作为哈希表,通过key计算出哈希值,然后直接就能获取哈希值所在处的元素了,效率很高。
顺序表的按值查找 LocateElem(L,e): 按值查找操作。在表L中查找具有给定关键字值的元素 平均时间复杂度 =O(n)
#define InitSize 10 //定义最大长度 typedef struct{ ElemTyp *data; //用静态的“数组”存放数据元素 int Length; //顺序表的当前长度 }SqList; //在顺序表L中查找第一个元素值等于e的元素,并返回其位序 int LocateElem(SqList L, ElemType e){ for(int i=0; i<L.lengthl i++) if(L.data[i] == e) return i+1; //数组下标为i的元素值等于e,返回其位序i+1 return 0; //推出循环,说明查找失败 } //调用LocateElem(L,9)
待完善。
更多待补充。
注意
单纯的顺序表,在结构体定义时并没有头指针和尾指针。
队列是有头指针和尾指针的,这点要注意区别。
如何表示一个顺序表?直接定义一个顺序表的结构体,然后声明一个结构体变量或者指针(结构体一般传递指针的情况比较多,一般可以定义变量,然后如果需要指针就再获取变量的指针,如果直接定义指针,那么想要变量的时候就没有了),这个变量就表示一整个的顺序表。这是一个整体。
而链表是以结点为基本单位的,我们定义的是结点的结构体,并不是整个的链表,因为链表是动态变化的,并不能一开始就固定下来,我们如果需要一个链表,就定义一个结点指针,用来表示一个链表的开始,也就是用来表示一个链表。
在实现数据结构的时候,思路一般是这样的:
#先定义数据结构的结构体;
#然后实现初始化;
#然后实现增删改查
@@增:指定位置插入,包括指定元素的前后,也包括头插和尾插这两种特殊情况;
@@删:删除指定位置的元素;
@@改:修改指定位置的元素;
@@查:获取指定位置的元素或者遍历;
这里面的指定的位置要怎么理解呢?
比如指定第i个位置,或者指定某个元素?比如顺序表中插入指定下标的位置,又比如在链表中插入某个指定元素的后面,等等。
实战总结
暂附上一个自定义的顺序表
#include <string.h> #include <stdio.h> #define SQLIST_DATATYPE int #define ELEMENT_MAX_COUNT 10 typedef struct sqlist { SQLIST_DATATYPE sq_list_content[ELEMENT_MAX_COUNT]; int current_len; }sqlist; void sqlist_init(sqlist *sq_list) { memset(sq_list, 0, sizeof(sqlist)); sq_list->current_len = 0; } //position start from one(1); int sqlist_insert_by_position(sqlist *sq_list, int position, SQLIST_DATATYPE data) { if(sq_list->current_len >= ELEMENT_MAX_COUNT) { return -1; } if(position < 1 || position > ELEMENT_MAX_COUNT) { return -2; } if(position > (sq_list->current_len + 1)) { return -3; } for(int i = position - 1; i < sq_list->current_len; i++) { sq_list->sq_list_content[i + 1] = sq_list->sq_list_content[i]; } sq_list->sq_list_content[position] = data; sq_list->current_len++; return 0; } int sqlist_delete_by_position(sqlist *sq_list, int position) { if(position > sq_list->current_len) { return -1; } for(int i = position; i < sq_list->current_len; i++) { sq_list->sq_list_content[i - 1] = sq_list->sq_list_content[i]; } sq_list->current_len--; return 0; } int sqlist_modify_by_position(sqlist *sq_list, int position, SQLIST_DATATYPE data) { if(position > sq_list->current_len) { return -1; } sq_list->sq_list_content[position - 1] = data; return 0; } SQLIST_DATATYPE sqlist_get_element_in_position(sqlist *sq_list, int position) { if(position > sq_list->current_len) { return -1; } return sq_list->sq_list_content[position - 1]; } void sqlist_print_all_element(sqlist *sq_list) { for(int i = 0; i < sq_list->current_len; i++) { printf("%d\n", sq_list->sq_list_content[i]); } }
更多的以后再慢慢优化总结。
在编写顺序表的按位置插入的代码时,一般都需要对插入位置进行判断。
一开始我在这部分产生了一些错误理解。
我在判断插入位置时:
先判断顺序表是不是已经满了,也就是size>=maxconut;已满则无法插入;
然后在判断插入位置是不是在1~maxconut之间,如果超出则无法插入;
之后,我产生了一个疑惑,什么疑惑呢?
假设maxconut是100,当前size是20,此时,我能否往不连续的位置插入?
比如:
通常来说是不可以的,因为这样的话,元素会丢失连续性。这个比较好理解。
于是,我就又对插入位置做了个判断,pos>size时不能插入;
所以,一共就有了三个判断。
于是,我的疑惑来了:网上看到的所有顺序表的实现,都没有对这一点的判断,于是我就想,难道不需要保持连续性?不合理呀?
问题出在哪里呢?
后来终于想明白了。
首先判断顺序表是否满没啥问题,而且可以独立出来一个判断函数。
至于pos的范围,如果只限制在有效范围内,那直接判断是不是在0~size之间不就行了!!!
这样一来,就合理了。