一、顺序表
1、基本概念
- 顺序表:顺序存储的线性表。
- 链式表:链式存储的线性表,简称链表。
- 图解:
顺序存储就是将数据存储到一片连续的内存中,在C语言环境下,可以是具名的栈数组,或者是匿名的堆数组。
存储方式不仅仅只是提供数据的存储空间,而是必须要能体现数据之间的逻辑关系。当采用顺序存储的方式来存放数据时,唯一能用来表达数据间本身的逻辑关系的就是存储位置。
2、顺序表的设计
a、顺序表结构体设计
- 顺序表总容量
- 顺序表当前最末元素下标位置
- 顺序表指针(指向的内存)
- 图解:
- 示例代码:
-
typedef struct sequence_list { int capacity; // 顺序表的容量 int last; // 顺序表的元素下标(最末尾元素的下标) int *data_p; // 顺序表内存(指针指向的内存区域) --- 以整型数据为例 }sq_list_t, *sq_list_p;
b、初始化顺序表
所谓初始化就是建立一个不包含任何元素的顺序表,设置好管理结构体中的表的总容量、末元素下标,申请好顺序表内存空间等系列准备工作。
- 图解:
- 示例代码:
-
-
/** * @brief 初始化顺序表 * @note None * @param cap_size:顺序表的容量 * @retval 成功:返回指向这个顺序表内存的指针 * 失败:返回NULL */ sq_list_p SQUENCE_LIST_Init(int cap_size) { // 1、给顺序表管理结构体申请一个堆内存空间 sq_list_p p = malloc(sizeof(struct sequence_list)); bzero(p, sizeof(struct sequence_list)); // 2、给申请的空间进行赋值操作 if ( p != NULL) { p->capacity = cap_size; // 顺序表的内存空间的容量 p->last = -1; // 顺序表的元素个数(初始化时,顺序表里面没有数据,所以是-1) p->data_p = malloc(sizeof(int)*cap_size); // 申请顺序表的内存空间(内存空间的大小由cap_size来决定) // (再乘sizeof(数据类型)是为了保证空间不浪费,访问不出错) bzero(p->data_p , sizeof(int)*cap_size); if ( p->data_p == NULL) // 判断是否申请空间失败 { free(p); return NULL; } } else // 判断是否申请空间失败 { return NULL; } // 3、将申请的空间(顺序表管理结构体堆空间)的地址返回 return p; }
c、销毁顺序表
一个顺序表最后不再需要,应当要释放其所占用的内存空间,这被称为顺序表的销毁。
- 图解:
-
-
/** * @brief 销毁顺序表 * @note None * @param p:顺序表管理结构体指针 * @retval None */ void SQUENCE_LIST_UnInit(sq_list_p p) { // 1、如果顺序表本身就为NULL,就不必继续下面的内容了 if ( p == NULL) return; // 2、释放内存空间(堆区)(由内到外) free(p->data_p); free(p); }
d、判断顺序表是否满了或空了
- 图解:
-
-
示例代码:
/** * @brief 判断顺序表是否数据为满 * @note None * @param p: 顺序表管理结构体指针 * @retval 顺序表数据满了:返回true * 顺序表数据未满:返回false */ bool SQUENCE_LIST_IfFull(sq_list_p p) { return p->last == p->capacity-1; } /** * @brief 判断顺序表是否数据为空 * @note None * @param p: 顺序表管理结构体指针 * @retval 顺序表数据为空:返回true * 顺序表数据未空:返回false */ bool SQUENCE_LIST_IfEmpty(sq_list_p p) { return p->last == -1; }
e、向顺序表中的表头插入一个数据
- 图解:
- 示例代码:
-
-
/** * @brief 在顺序表中的表头插入一个数据 * @note None * @param p: 顺序表管理结构体指针 * new_data: 要插入的数据 * @retval 成功:返回0 * 失败:返回-1 */ int SQUENCE_LIST_InsertData(sq_list_p p, int new_data) { // 1、判断顺序表数据是否满了,满了返回-1 if ( SQUENCE_LIST_IfFull(p)) return -1; // 2、将原有的数据全部往后挪一位,再将数据插入 for (int i = p->last; i >=0; i--) { p->data_p[i+1] = p->data_p[i]; } // 3、将数据插入到表头 p->data_p[0] = new_data; // 4、顺序表的元素下标(last)+1 p->last++; // 5、成功返回0 return 0; }
f、遍历顺序表
- 图解:
-
-
示例代码:
/** * @brief 遍历顺序表 * @note None * @param p: 顺序表管理结构体指针 * @retval None */ void SQUENCE_LIST_ShowList(sq_list_p p) { printf("==============顺序表里面的数据==============\n\n"); for (int i = 0; i <= p->last; i++) { printf("顺序表里面的内存的数据data[%d] == %d\n", i, p->data_p[i]); } printf("==========================================\n\n"); }
g、将顺序表指定的数据删除
- 图解:
- 示例代码:
-
-
/** * @brief 将顺序表指定的数据删除 * @note 根据下标(位置)来删除 * @param p: 顺序表管理结构体指针 * data_pos:要删除的顺序表数据的位置 * @retval 成功:返回0 * 失败:返回-1 */ int SQUENCE_LIST_DelPosData(sq_list_p p, int data_pos) { // 1、如果顺序表里面没有数据,就返回-1 if(SQUENCE_LIST_IfEmpty(p)) return -1; // 2、根据要删除的数据的位置,将其后面的数据全部往前挪动一位即可 for (int i = data_pos; i <= p->last; i++) { p->data_p[i] = p->data_p[i+1]; } // 3、将下标减1 p->last--; // 4、成功删除数据,返回0 return 0; }
h、修改顺序表中的数据
- 图解:
- 示例代码:
-
-
/** * @brief 修改顺序表中的数据 * @note 根据数据的位置来修改数据 * @param p: 顺序表管理结构体指针 * data_pos:要修改的顺序表数据的位置 * @retval 成功:返回0 * 失败:返回-1 */ int SQUENCE_LIST_ChangeData(sq_list_p p, int data_pos, int new_data) { // 1、判断顺序表是否为空,且输入的数据位置不得超过下标位置 if ( SQUENCE_LIST_IfEmpty(p) || (data_pos > p->last)) return -1; // 2、根据位置,修改数据 p->data_p[data_pos] = new_data; // 3、成功返回0 return 0; }
3、顺序表优缺点总结
顺序存储中,由于逻辑关系是用物理位置来表达的,因此从上述示例代码可以很清楚看到,增删数据都非常困难,需要成片地移动数据。顺序表对数据节点的增删操作是很不友好的。
- 优点
- 不需要多余的信息来记录数据间的关系,存储密度高
- 所有数据顺序存储在一片连续的内存中,支持立即访问任意一个随机数据,比如上述顺序表中中第i个节点是 p->data[i]
- 缺点
- 插入、删除时需要保持数据的物理位置反映其逻辑关系,一般需要成片移动数据
- 当数据节点数量较多时,需要一整片较大的连续内存空间
- 当数据节点数量变化剧烈时,内存的释放和分配不灵活