数据结构之顺序表:从内存角度解析线性表的基本实现
————最后附有全部代码 ————
数据结构是计算机科学中的重要概念,它研究数据的组织、存储和操作方式。其中,顺序表是一种常见的线性表数据结构,它以连续的内存空间存储元素,并通过下标进行访问。本文将详细介绍顺序表的概念、基本实现以及优缺点,帮助读者深入理解顺序表的内部原理和使用方法。
1.顺序表的概念
顺序表是一种线性表的存储方式,它通过连续的内存空间存储元素。顺序表中的元素在内存中按照顺序排列,可以通过下标访问元素。顺序表具有固定的容量,一旦达到容量上限,需要进行扩容操作。
2.顺序表的基本实现
将完整程序分成三个程序来实现,①SeqList.h ②SeqList.c ③SeqList_main.c
①初始化顺序表
②判断顺序表是否已满
③判断顺序表是否为空
④插入元素(1.在头节点插入、2.在尾节点插入、3.在任意位置插入)
⑤删除元素(1.删除头节点、2.删除尾节点、3.删除任意节点)
⑥查找元素(1.按值查找、2. 按下标查找)
⑦修改元素(通过下标访问元素并更新该值)
⑧获取顺序表长度
⑨遍历顺序表
⑩清空表
具体分析
①初始化顺序表
②判断顺序表是否已满
③判断顺序表是否为空
④插入元素(1.在头节点插入、2.在尾节点插入、3.在任意位置插入)
1.在头节点插入
2.在尾节点插入
3.在任意位置插入
⑤删除元素(1.删除头节点、2.删除尾节点、3.删除任意节点)
1.删除头节点
2.删除尾节点
3.删除任意节点
⑥查找元素(1.按值查找、2. 按下标查找)
1.按值查找
2.按下标查找
⑦修改元素(通过下标访问元素并更新该值)
⑧获取顺序表长度
⑨遍历顺序表
⑩清空表
3.优点
随机访问性:顺序表通过下标进行元素访问,具有高效的随机访问性能。
修改、和查询算法比较简单
内存连续性:顺序表使用连续的内存空间存储元素,对于大规模数据的处理更加高效。
4.缺点
容量限制:顺序表的容量是固定的,一旦达到容量上限,需要进行扩容操作,可能涉及数据的搬迁和内存重新分配。
插入和删除操作的效率低:在顺序表中插入或删除元素时,需要移动其他元素的位置,导致时间复杂度较高。
5.总结
顺序表作为一种常见的线性表数据结构,通过连续的内存空间存储元素,提供了高效的随机访问能力。在插入和删除操作上可能存在效率低下和容量限制的问题,因此在实际应用中需要根据具体需求选择合适的数据结构。通过了解顺序表的基本实现和特点,我们能够更好地理解其内部原理,并能够灵活应用于实际开发中。
①SeqList.h
#ifndef _SEQLIST_H_
#define _SEQLIST_H_
#include <stdio.h>
#include <stdlib.h>
//声明一个顺序表结构体
typedef struct SeqList {
int data;//数据域
int tail;//记录节点个数
int capacity;//记录表容量
struct SeqList *next;//指针域
} SeqList, *PSeqList;
// 初始化顺序表
void init(PSeqList *list, int capacity);
// 判断顺序表是否为空
int isEmpty(PSeqList list);
// 判断顺序表是否已满
int isFull(PSeqList list);
// 在头部插入元素
void insertAtHead(PSeqList *list, int value);
// 在尾部插入元素
void insertAtTail(PSeqList *list, int value);
// 在任意下标插入元素
void insertAtPosition(PSeqList *list, int position, int value);
// 删除头节点
void deleteAtHead(PSeqList *list);
// 删除尾节点
void deleteAtTail(PSeqList *list);
// 删除任意下标的节点
void deleteAtPosition(PSeqList *list, int position);
// 按值查找元素的下标
int searchByValue(PSeqList list, int value);
// 按下标查找元素的值
int searchByIndex(PSeqList list, int position);
// 修改指定下标的元素值
void modify(PSeqList list, int position, int value);
// 获取顺序表的长度
int getLength(PSeqList list);
// 遍历顺序表
void traverse(PSeqList list);
//释放顺序表内存
void freeList(PSeqList *list);
#endif
②SeqList.c
#include "Seqlist.h"
// 初始化顺序表
void init(PSeqList *list, int capacity){
*list = (PSeqList)malloc(sizeof(SeqList));
if(NULL == *list){
perror("init malloc");
return ;
}
(*list)->tail = 0;
(*list)->capacity = capacity;
(*list)->next = NULL;
}
// 判断顺序表是否为空
int isEmpty(PSeqList list){
//容错判断
if(NULL == list){
puts("isEmpty arg err!");
return -1;
}
//判断头节点的下一个节点是否为空
if(list == NULL || list->tail == 0){
return 1;//真空
}else{
return 0;//非空
}
}
// 判断顺序表是否已满
int isFull(PSeqList list){
if(NULL == list){
puts("isFull arg err");
return -1;
}
if(list == NULL || list->tail == list->capacity){
puts("顺序表已满!");
return 1;//顺序表已满
}else{
return 0;//顺序表未满
}
}
// 在头部插入元素
void insertAtHead(PSeqList *list, int value){
//容错判断
if(NULL == *list){
puts("insert head arg err!");
return ;
}
//判满
if(isFull(*list)){
//puts("表已满");
return ;
}
PSeqList newNode = (PSeqList)malloc(sizeof(SeqList));//开辟一个新节点newNode
if(newNode == NULL){
puts("insert head malloc");
return ;
}
newNode->data = value;//将新值赋值给新节点的数据区
newNode->next = (*list)->next;//先将新节点的下一个节点连接好
(*list)->next = newNode;//再将头节点连接到新节点上
(*list)->tail++;//表中的节点个数加一
}
// 在尾部插入元素
void insertAtTail(PSeqList *list, int value){
//容错判断
if(NULL == *list){
puts("insert tail arg err!");
return ;
}
//判满
if(isFull(*list)){
//puts("表已满");
return ;
}
PSeqList newNode = (PSeqList)malloc(sizeof(SeqList));//开辟一个新节点newNode
if(newNode == NULL){
perror("insert tail malloc");
return ;
}
newNode->data = value;//赋值新值
newNode->next = NULL;//在尾巴上插入,则该节点的下一个节点就是NULL,先连接上;
PSeqList current = *list;//重新定义一个新的结构体指针current指向头节点
//循环判断插入的位置,找到表的尾节点
while(current->next != NULL){//current的下一个节点不为NULL,则循环,直到找到一个节点的下一个节点是NULL为止
current = current->next;//current指针不断向后移一位
}
current->next = newNode;//current指针此时的位置是尾节点,将它指向新开辟的newNode节点
(*list)->tail++;//插入后表的节点数加一
}
// 在任意位置插入元素
void insertAtPosition(PSeqList *list, int position, int value){
//容错判断
if(NULL == *list){
puts("insert position arg err!");
return ;
}
//判满
if(isFull(*list)){
//puts("表已满");
return ;
}
//判断插入的位置是否有效
if(position < 0 || position > (*list)->tail + 1){//位置在头节点、小于头节点或位置大于表的长度
puts("没有该下标,位置无效!");
return ;
}
PSeqList newNode = (PSeqList)malloc(sizeof(SeqList));//开辟一个新节点newNode
if(newNode == NULL){
perror("insert position malloc");
return ;
}
newNode->data = value;//赋值新值
PSeqList current = *list;//重新定义一个新的结构体指针current指向头节点
//遍历position位置
for(int i = 0; i < position - 1; i++){
current = current->next;//current指针往后移动
}
newNode->next = current->next;//先将新节点连接插入的位置的下一个节点
current->next = newNode;//再将新节点与前一节点连接
(*list)->tail++;//插入后表中的节点数加一
}
// 删除头节点
void deleteAtHead(PSeqList *list){
//容错判断
if(NULL == *list){
puts("delete head arg err!");
return ;
}
//判断表是否为空
if(isEmpty(*list)){
puts("表是空的,无法删除!");
return ;
}
PSeqList current = (*list)->next;//重新定义一个新的结构体指针current指向头节点的下一个节点(就是定义current记录将要删除的节点
(*list)->next = current->next;//直接断掉current节点,将头节点指向NULL
free(current);//释放删除的节点current的内存
(*list)->tail--;//删除节点后表的节点数减一
}
// 删除尾节点
void deleteAtTail(PSeqList *list){
//容错判断
if(NULL == *list){
puts("delete head arg err!");
return ;
}
//判断表是否为空
if(isEmpty(*list)){
puts("表是空的,无法删除!");
return ;
}
PSeqList current = *list;//重新定义一个新的结构体指针current指向头节点
//寻找尾节点
while(current->next->next != NULL){//如果current != NULL
current = current->next;//current指针往后
}
PSeqList tailNode = current->next;//重新定义一个新的节点tail指向被删除节点(current是被删除节点的前一节点)
current->next = NULL;//断开后重新连接
free(tailNode);//释放已被删除的节点内存
(*list)->tail--;//节点数减一
}
// 删除任意位置的节点
void deleteAtPosition(PSeqList *list, int position){
//容错判断
if(NULL == *list){
puts("delete position arg err!");
return ;
}
//判断表是否为空
if(isEmpty(*list)){
puts("表是空的,无法删除!");
return ;
}
PSeqList current = *list;//重新定义一个新的结构体指针current指向头节点
for(int i = 0; i < position; i++){
current = current->next;
}
PSeqList deleteNode = current->next;//
current->next = deleteNode->next;//
free(deleteNode);
(*list)->tail--;
}
// 按值查找元素的下标
int searchByValue(PSeqList list, int value){
//容错判断
if(NULL == list){
puts("ceach ByValue arg err!");
return -1;
}
PSeqList current = list->next;//重新定义一个新的结构体指针current指向头节点的下一个节点
int position = 0;//定义一个下标position初始化为0
while(current != NULL){//节点存在时进入while循环
if(current->data == value){//如果节点的数据==查询的的数据
return position;//返回对应节点下标
}
current = current->next;//节点往后移动
position++;
}
return -1;//未找到
}
// 按下标查找元素的值
int searchByIndex(PSeqList list, int position){
//容错判断
if(NULL == list){
puts("ceach ByIndex arg err!");
return -1;
}
if(position < 0 || position > list->tail){
puts("下标无效!");
return -1;
}
PSeqList current = list->next;//
for(int i = 0; i < position; i++){
current = current->next;
}
return current->data;//将返回查找到的下标的对应的值
}
// 修改指定下标的元素值
void modify(PSeqList list, int position, int value){
//容错判断
if(NULL == list){
puts("modify arg err!");
return ;
}
//判断表是否为空
if(isEmpty(list)){
puts("表是空的,无法修改!");
return ;
}
if(position < 0 || position > list->tail){
puts("下标无效!");
return ;
}
PSeqList current = list->next;//重新定义一个新的结构体指针current指向头节点的下一个节点
for(int i = 0; i < position; i++){
current = current->next;
}
current->data = value;
}
// 获取顺序表的长度
int getLength(PSeqList list){
//容错判断
if(NULL == list){
puts("getLength arg err!");
return -1;
}
//判断表是否为空
if(isEmpty(list)){
puts("表是空的,长度为0");
return -1;
}
return list->tail;//直接返回节点数即表的长度
}
// 遍历顺序表
void traverse(PSeqList list){
//容错判断
if(NULL == list){
puts("traverse arg err!");
return ;
}
//判断表是否为空
if(isEmpty(list)){
puts("表是空的!");
return ;
}
PSeqList current = list->next;
while(current != NULL){
printf("%d ",current->data);
current = current->next;
}
printf("\n");
}
//释放顺序表内存(清空)
void freeList(PSeqList *list){
//容错判断
if(NULL == *list){
puts("freeList arg err!");
return ;
}
PSeqList current = (*list)->next;
while(current != NULL){
PSeqList temp = current;
current = current->next;
free(temp);
}
free(*list);
*list = NULL;
}
③Seqlist_main.c
#include "Seqlist.h"
#include "Seqlist.c"
int main(int argc, char *argv[])
{
PSeqList list;
int choice, value, position, capacity;
printf("请输入顺序表的大小:");
scanf("%d",&capacity);
init(&list, capacity);
while (1) {
printf("\n*****************************顺序表操作菜单*****************************\n");
printf("1. 在头部插入元素在 2. 尾部插入元素 3. 在任意下标插入元素\n");
printf("4. 删除头节点 5. 删除尾节点 6. 删除任意下标的节点\n");
printf("7. 按值查找元素的下标 8. 按下标查找元素的值 9. 修改指定下标的元素值\n");
printf("10. 获取顺序表的长度 11. 遍历顺序表 12. 清空表并退出程序\n");
printf("*****************************顺序表操作菜单*****************************\n");
printf("请输入操作编号:");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("请输入要插入的元素值:");
scanf("%d", &value);
insertAtHead(&list, value);
if(!isFull(list)){
printf("在头部插入元素 %d\n", value);
break;
}else{
printf("好了!顺序表已经被你填满了!\n");
}
break;
case 2:
printf("请输入要插入的元素值:");
scanf("%d", &value);
insertAtTail(&list, value);
if(!isFull(list)){
printf("在尾部插入元素 %d\n", value);
break;
}else{
printf("好了!顺序表已经被你填满了!\n");
}
break;
case 3:
printf("请输入要插入的下标:");
scanf("%d", &position);
printf("请输入要插入的元素值:");
scanf("%d", &value);
insertAtPosition(&list, position, value);
if(!isFull(list)){
printf("在下标 %d 插入元素 %d\n", position, value);
break;
}else{
printf("好了!顺序表已经被你填满了!\n");
}
break;
case 4:
deleteAtHead(&list);
if(!isEmpty){
printf("已经删除头节点\n");
break;
}
break;
case 5:
deleteAtTail(&list);
if(!isEmpty){
printf("已经删尾节点\n");
break;
}
break;
case 6:
printf("请输入要删除的下标:");
scanf("%d", &position);
deleteAtPosition(&list, position);
break;
case 7:
printf("请输入要查找元素下标的值: ");
scanf("%d", &value);
position = searchByValue(list, value);
if (position != -1) {
printf("%d 值的下标是: %d\n", value, position);
} else {
printf("在列表中没有找不到 %d\n", value);
}
break;
case 8:
printf("请输入要查找元素的下标: ");
scanf("%d", &position);
value = searchByIndex(list, position);
if (value != -1) {
printf("该下标 %d 的值是 %d\n", position, value);
} else {
printf("无效下标!\n");
}
break;
case 9:
printf("请输入要修改的元素下标: ");
scanf("%d", &position);
printf("请输入新的值: ");
scanf("%d", &value);
modify(list, position, value);
break;
case 10:
printf("该表的长度为 %d\n", getLength(list));
break;
case 11:
printf("表的所有元素为: ");
traverse(list);
break;
case 12:
freeList(&list);
printf("表已被清空...\n");
exit(0);
default:
printf("输入无效!请按照菜单选择!\n");
break;
}
}
return 0;
}