当前位置: 首页 > news >正文

数据结构--顺序表与链表

1.顺序表特点与基本操作

1.1 顺序表定义

顺序表是一种线性表的存储结构,采用连续的内存空间存储数据元素。其逻辑顺序与物理顺序一致,通过数组实现,支持随机访问。顺序表属于静态存储结构,但可通过动态分配内存实现扩容。

1.2 顺序表特点

内存连续

所有元素存储在地址连续的存储单元中,通过首地址和下标可直接计算出元素位置,访问时间复杂度为 O(1)。

预先分配空间
需提前定义最大容量,静态分配可能导致空间浪费或不足。动态分配虽可扩容,但需重新申请内存并迁移数据,时间复杂度为O(n)。

插入删除效率低
在非尾部位置操作时,需移动后续元素以保持连续性。平均时间复杂度为 O(n),尾部操作则为 O(1)。

1.3 顺序表类型定义

// 定义顺序表结构体
typedef struct {int *elem;       // 指向存储元素的动态数组基地址int length;      // 当前顺序表中元素的实际个数int listsize;    // 当前顺序表分配的存储空间大小(最大能容纳的元素个数)
} sqlist;

1.4 初始化顺序表

/*** 初始化顺序表* @param L 待初始化的顺序表(引用传递,修改会影响实参)* @param n 初始要存入的元素个数* @return 1-初始化成功;0-内存分配失败*/
int init(sqlist &L,int n) {// 为顺序表分配能存储100个int元素的初始空间L.elem = (int*)malloc(100 * sizeof(int));if (!L.elem) {  // 内存分配失败(返回NULL)return 0;}L.length = 0;   // 初始元素个数为0// 循环读取n个元素存入顺序表for (int i = 0; i < n; i++) {scanf("%d", &(L.elem[i]));  // 读取元素到数组对应位置L.length++;                 // 每存入一个元素,实际长度加1}L.listsize = 100;  // 记录当前分配的最大容量return 1;  

1.5 插入操作

/*** 向顺序表中插入元素* @param L 目标顺序表(引用传递)* @param i 插入位置(逻辑位置,从1开始计数)* @param e 要插入的元素值* @return 1-插入成功;0-位置不合法或内存扩容失败*/int insert(sqlist &L, int i, int e) {int *newspace;  // 用于扩容时指向新的内存空间int *p;         // 指向需要移动的元素int *q;         // 指向插入位置// 检查插入位置合法性:必须在1到当前长度+1之间(允许插在表尾)if (i < 1 || i > L.length + 1) {return 0;}// 若当前元素个数已达最大容量,需要扩容if (L.length >= L.listsize) {// 重新分配内存:在原有大小基础上增加10个元素的空间newspace = (int*)realloc(L.elem, (L.listsize + 10) * sizeof(int));if (!newspace) {  // 扩容失败return 0;}L.elem = newspace;  // 指向新的内存空间L.listsize += 10;   // 更新最大容量}q = &(L.elem[i - 1]);  // 计算插入的物理位置(逻辑位置i对应数组下标i-1)// 从最后一个元素开始,到插入位置为止,依次向后移动一个位置// (避免覆盖后续元素,必须从后往前移)for (p = &(L.elem[L.length - 1]); p >= q; --p) {*(p + 1) = *p;  // 后移元素}*q = e;      // 在插入位置存入新元素++L.length;  // 实际长度加1return 1;    // 插入成功
}

图像解释与平均移动次数

1.6 删除操作

/*** 从顺序表中删除元素* @param L 目标顺序表(引用传递)* @param i 要删除的元素位置(逻辑位置,从1开始计数)* @param e 用于保存被删除的元素值(输出参数)* @return 1-删除成功;0-位置不合法*/int dele(sqlist &L, int i, int &e) {int *q;  // 指向顺序表最后一个元素int *p;  // 指向要删除的元素// 检查删除位置合法性:必须在1到当前长度之间if (i < 1 || i > L.length) {return 0;}p = &(L.elem[i - 1]);       // 找到要删除元素的物理位置(下标i-1)e = *p;                     // 保存被删除的元素值q = L.elem + L.length - 1;  // 指向最后一个元素(下标length-1)// 从删除位置的下一个元素开始,到最后一个元素为止,依次向前移动一个位置for (++p; p <= q; ++p) {*(p - 1) = *p;  // 前移元素,覆盖被删除的位置}--L.length;  // 实际长度减1return 1;  // 删除成功
}

算法时间主要花费在移动元素上

若删除尾节点,则根本无需移动,特别快。

若删除首节点,n-1个元素全部需要前移,特别慢。

1.7 查找元素位置

输入元素值,返回该元素值的位置

/*** 查找顺序表中指定元素的位置* @param L 目标顺序表(值传递,不修改原表)* @param e 要查找的元素值* @return 元素的逻辑位置(从1开始);0-未找到*/int locate(sqlist L, int e) {int i = 1;        // 记录逻辑位置(初始为第一个元素)int *p = L.elem;  // 指向第一个元素的指针// 循环遍历元素:未超出长度且未找到目标时继续while ((i <= L.length) && (*p != e)) {i++;  // 位置后移p++;  // 指针后移(指向下一个元素)}// 若找到(i未超出长度),返回逻辑位置;否则返回0if (i <= L.length) {return i;} else {return 0;}
}

1.8 遍历打印顺序表

void display(sqlist L) {for (int i = 0; i < L.length; i++) {printf("%d ", L.elem[i]);  }printf("\n"); 

1.9 完整代码

#include<stdio.h>
#include<stdlib.h>typedef struct {int *elem;int length;int listsize;
} sqlist;// 初始化顺序表
int init(sqlist &L,int n) {L.elem=(int*)malloc(100*sizeof(int));if(!L.elem) return 0;  // 内存分配失败L.length=0;for(int i=0; i<n; i++) {scanf("%d",&(L.elem[i]));L.length++;}L.listsize=100;return 1;
}// 插入元素
int insert(sqlist &L,int i,int e) {int *newspace;int *p;int *q;if(i<1||i>L.length+1) return 0;  // 位置不合法if(L.length>=L.listsize) { // 需要扩容newspace=(int*)realloc(L.elem,(L.listsize+10)*sizeof(int));if(!newspace) return 0;  // 扩容失败L.elem=newspace;L.listsize+=10;}q=&(L.elem[i-1]);  // 插入位置// 从后往前移动元素for(p=&(L.elem[L.length-1]); p>=q; --p) {*(p+1)=*p;}*q=e;  // 插入新元素++L.length;return 1;
}// 删除元素
int dele(sqlist &L,int i,int &e) {int *q;int *p;if(i<1||i>L.length) return 0;  // 位置不合法p=&(L.elem[i-1]);e=*p;  // 保存要删除的元素q=L.elem+L.length-1;  // 最后一个元素位置// 从删除位置往后移动元素for(++p; p<=q; ++p) *(p-1)=*p;--L.length;return 1;
}// 查找元素
int locate(sqlist L,int e) {int i=1;int *p=L.elem;while((i<=L.length)&&(*p!=e)) {i++;p++;};if(i<=L.length) return i;  // 找到返回位置else return 0;  // 未找到返回0
}// 显示链表内容
void display(sqlist L) {for(int i=0; i<L.length; i++)printf("%d ",L.elem[i]);printf("\n");
}int main() {sqlist p;int i,e,n;int s;  // 操作选择printf("输入建立顺序表大小n:\n");scanf("%d",&n);printf("输入顺序表的值(空格分隔):\n");if(!init(p,n)) { // 检查初始化是否成功printf("初始化失败!\n");return 1;}printf("建立的顺序表表为:\n");display(p);do {printf("\n请选择操作:\n");printf("1. 插入元素\n");printf("2. 删除元素\n");printf("3. 查找元素\n");printf("0. 退出程序\n");scanf("%d",&s);  // 修正:添加取地址符&switch(s) {case 1:printf("请输入插入位置和元素值(用空格分隔):\n");scanf("%d%d",&i,&e);if(insert(p,i,e)) {printf("插入成功!当前链表为:\n");display(p);} else {printf("插入失败!位置不合法或内存不足\n");}break;case 2:printf("请输入要删除的位置:\n");scanf("%d",&i);if(dele(p,i,e)) {printf("删除成功!删除的元素是:%d\n",e);printf("当前链表为:\n");display(p);} else {printf("删除失败!位置不合法\n");}break;case 3:printf("请输入要查找的元素值:\n");scanf("%d",&e);i = locate(p,e);if(i!=0) {printf("元素%d的位置是:%d\n",e,i);} else {printf("未找到元素%d\n",e);}printf("当前链表为:\n");display(p);break;case 0:printf("操作结束,退出程序\n");break;default:printf("输入错误,请重新选择\n");}} while(s!=0);// 释放动态分配的内存free(p.elem);return 0;
}

1.10 顺序表的优缺点

顺序表优点

存储密度大,存储效率高
顺序表采用连续的内存空间存储数据,无需额外的指针或链接信息,存储密度接近100%,空间利用率高。

随机访问快速
通过下标可在 O(1) 时间内直接访问任意元素,适合频繁查询或需要快速定位的场景。

顺序表缺点

插入,删除效率低
在非尾部位置插入或删除元素时,需要移动大量元素以保证连续性,时间复杂度为 O(n)。

固定容量限制
静态存储形式,元素个数不能自由扩充。静态分配内存的顺序表需预先指定最大容量,可能导致空间浪费或溢出;动态分配虽可扩容,但需复制全部数据,开销较大。

2.链表特点与基本操作

2.1 链表的定义

链表是一种线性数据结构,由一系列节点(Node)组成,每个节点包含两部分:

  • 数据域:存储实际的数据。
  • 指针域:存储指向下一个节点的地址(或引用)。
    链表通过指针将节点按逻辑顺序连接,形成链式结构。

2.2 链表的特点

动态内存分配

  • 链表的节点在内存中不必连续存储,通过指针动态关联,因此长度可灵活调整。

插入与删除高效

  • 时间复杂度为 O(1)(若已知操作位置的前驱节点)。
  • 无需像数组那样移动大量元素。

随机访问效率低

  • 需从头节点开始遍历,时间复杂度为 O(n)

链式存储结构

  • 节点在存储器上的位置是任意的,逻辑上相邻的元素在物理上不一定会相邻。
  • 节点内部存储空间不连续,节点之间存储空间连续。

2.3单链表的定义与表示

// 定义单链表节点结构体
typedef struct lnode{int data;               // 节点存储的数据struct lnode *next;     // 指向后继节点的指针
}lnode, *link;              // lnode为节点类型,link为节点指针类型(指向lnode的指针)

2.4 创建链表

2.4.1 逆序创建(头插法)

/*** 逆序创建单链表(头插法)* 功能:输入n个元素,按逆序插入到链表中(新元素始终插在头节点之后)* @param L 链表头指针的引用(通过引用修改实参的头指针)* @param n 要创建的链表节点个数* @return 1-创建成功*/int f_creat(link &L,int n){int i;struct lnode *p;  // 临时指针,用于指向新创建的节点// 创建头节点L=(link)malloc(sizeof(lnode));L->next=NULL;     // 头节点初始后继为NULL(空链表状态)// 循环创建n个节点for(int i=0;i<n;i++){p=(link)malloc(sizeof(lnode));  // 为新节点分配内存scanf("%d",&p->data);          // 头插法核心:新节点的后继指向当前头节点的后继p->next=L->next;// 头节点的后继指向新节点(将新节点插入到头节点之后)L->next=p;}return 1;  // 创建成功
} 

2.4.2 正序创建(尾插法)

/*** 正序创建单链表(尾插法)* 功能:输入n个元素,按输入顺序依次插入到链表尾部,最终链表顺序与输入顺序一致* @param L 链表头指针的引用(用于修改外部头指针)* @param n 要创建的节点个数* @return 1-创建成功*/int t_creat(link &L, int n) {// 1. 创建头节点L = (link)malloc(sizeof(lnode));  L->next = NULL;                    // 头节点初始后继为NULL(空链表状态)// 2. 定义尾指针p,初始指向头节点(此时头节点是链表的最后一个节点)link p = L;                       // 3. 循环创建n个节点,并按顺序插入到链表尾部for (int i = 0; i < n; i++) {// 创建新节点slink s = (link)malloc(sizeof(lnode));  scanf("%d", &s->data);                 // 将新节点s链接到当前尾节点p的后面p->next = s;                        // 尾指针p移动到新节点s(更新尾节点为s)p = s;  }// 4. 链表创建完成后,将最后一个节点的后继置为NULL(标记链表结束)p->next = NULL;return 1;  
}

2.5 获取元素

/*** 获取链表中第i个元素的值* @param L 链表头指针(头节点)* @param i 要获取的元素位置(逻辑位置,从1开始计数)* @param e 用于存储获取到的元素值(输出参数)* @return 1-获取成功;0-位置不合法(i超出范围)*/
int get(link L,int i ,int &e){link p;       // 遍历指针,用于指向当前节点p=L->next;    // 从第一个数据节点(头节点的后继)开始遍历int j=1;      // 记录当前遍历到的位置(初始为第一个节点)// 循环找到第i个节点:p不为空且未到达第i个节点时继续后移while(p && j<i){p=p->next;  // 指针后移,指向下一个节点j++;        }if(!p || j>i) return 0;e=p->data;  // 找到第i个节点,将数据存入ereturn 1;   // 获取成功
}

2.6 插入元素

在第i个位置插入元素e,要先找到第i-1个位置

核心步骤:

 s->next=p->next;  p->next=s;   

上述两行代码不能交换位置

/*** 在链表第i个位置插入元素e* @param L 链表头指针(头节点)* @param i 插入位置(逻辑位置,从1开始,可插在表尾)* @param e 要插入的元素值* @return 1-插入成功;0-位置不合法*/int insert(link L,int i,int e){struct lnode *s;  // 指向新创建的插入节点struct lnode *p;  // 遍历指针,用于找到第i-1个节点(插入位置的前驱)int j=0;          // 记录当前位置(初始为头节点,位置0)p=L;              // 从头部点开始遍历// 找到第i-1个节点:p不为空且未到达目标位置时继续后移while(p && j<i-1){p=p->next;  // 指针后移j++;        // 位置计数加1}if(!p || j>i-1) return 0;// 创建新节点并赋值s=(link)malloc(sizeof(lnode));s->data=e;// 插入核心操作:先连后,再连前(避免断链)s->next=p->next;  // 新节点的后继指向p的原后继p->next=s;        // p的后继指向新节点return 1;  /
}

2.7 删除节点

核心步骤:

p->next=p->next->next

用q记录删除的节点,以便于释放内存

/*** 删除链表中第i个元素* @param L 链表头指针的引用* @param i 要删除的元素位置(逻辑位置,从1开始)* @param e 用于存储被删除的元素值(输出参数)* @return 1-删除成功;0-位置不合法*/int del(link &L,int i,int &e){struct lnode *p;  // 遍历指针,用于找到第i-1个节点(删除位置的前驱)p=L;              // 从头部点开始遍历struct lnode *q;  // 指向要删除的节点int j=0;          // 记录当前位置(初始为头节点,位置0)// 找到第i-1个节点:p的后继不为空(确保有第i个节点)且未到达目标位置while(p->next && j<i-1){   p=p->next;  j++;        }// 若p的后继为空(i超过链表长度)或j>i-1(i小于1),则位置不合法if(!p->next || j>i-1) return 0;q=p->next;        // q指向要删除的第i个节点p->next=q->next;  // 将p的后继指向q的后继(跳过q,实现删除)e=q->data;        // 保存被删除节点的数据free(q);          // 释放被删除节点的内存return 1;  
}

2.8 遍历打印链表

void see(link L){struct lnode *p;p=L;while(p->next){p=p->next;printf("%d ",p->data);}
}

2.9 完整代码

#include<stdio.h>
#include<stdlib.h>typedef struct lnode{int data;struct lnode *next;
}lnode,*link;//逆序创建链表 
int f_creat(link &L,int n){int i;struct lnode *p;L=(link)malloc(sizeof(lnode));L->next=NULL;for(int i=0;i<n;i++){p=(link)malloc(sizeof(lnode));scanf("%d",&p->data);p->next=L->next;L->next=p;}return 1;
} 
//正序创建链表
int t_creat(link &L, int n) {L = (link)malloc(sizeof(lnode));  L->next = NULL;                link p = L;                     for (int i = 0; i < n; i++) {link s = (link)malloc(sizeof(lnode));  scanf("%d", &s->data);                 p->next = s;                        p=s;  }p->next=NULL;return 1;
}int get(link L,int i ,int &e){link p;p=L->next;int j=1;while(p && j<i){p=p->next;j++;}if(!p || j>i) return 0;e=p->data;return 1;
}int insert(link L,int i,int e){struct lnode *s;struct lnode *p;int j=0;p=L;while(p && j<i-1){p=p->next;j++;}if(!p || j>i-1) return 0;s=(link)malloc(sizeof(lnode));s->data=e;s->next=p->next;p->next=s;return 1; 
}//删除第i个元素
int del(link &L,int i,int &e){struct lnode *p;p=L;struct lnode *q;int j=0;//p为第i-1个元素 while(p->next && j<i-1){   p=p->next;j++;}if(!p->next || j>i-1) return 0;q=p->next;p->next=q->next;e=q->data;free(q);return 1;	
} void see(link L){struct lnode *p;p=L;while(p->next){p=p->next;printf("%d ",p->data);}
}
int main()
{link p,L,s;int  data;int i,n,e;int select;printf("输入链表长度:\n");scanf("%d",&n);printf("输入链表的值,空格分隔:\n");t_creat(L,n);see(L);do{printf("\n1:取出\n");printf("2:插入\n");printf("3:删除\n");printf("0:结束!\n");printf("请输入选择\n");scanf("%d",&select);switch(select){case 1:printf("输入元素的位置i:\n");scanf("%d",&i);get(L,i,e);printf("取出的元素是:%d\n",e);break;case 2:printf("输入插入元素的位置i和值e,空格分隔\n");scanf("%d%d",&i,&e);insert(L,i,e);printf("插入后所有元素为:\n");see(L);break;case 3:printf("输入要删除的元素位次i:\n");scanf("%d",&i);del(L,i,e);printf("删除后所有元素为:\n");see(L);break;case 0:printf("操作结束");break;default: printf("选择出错");}}while(select!=0);p=L;while(p != NULL){link temp = p;p = p->next;free(temp);}return 0;
}

2.10 链表的优缺点

链表的优点

动态大小
链表不需要预先分配固定大小的内存空间,可以根据实际需求动态调整大小,适合数据量变化频繁的场景。

高效插入和删除
在链表中插入或删除节点只需修改相邻节点的指针,时间复杂度为 O(1)(已知位置时)。相比之下,数组需要移动大量元素,时间复杂度为 O(n)。

内存利用率高
链表节点在内存中不必连续存储,可以充分利用零散的内存空间,避免数组因连续内存分配导致的碎片问题。

灵活的结构扩展
链表可以轻松扩展为双向链表、循环链表等变体,支持更复杂的操作(如反向遍历),而数组结构固定。

链表的缺点

存储密度小,随机访问效率低
链表不支持直接索引访问,查找第 i个元素需要从头节点开始遍历,时间复杂度为 O(n)。数组的随机访问时间为 O(1)。

额外内存开销
每个节点需存储指针(或引用),占用额外内存。例如,单链表每个节点比数组多一个指针的空间开销。

http://www.dtcms.com/a/524788.html

相关文章:

  • 网站排名优化课程深圳网站建设开发哪家好
  • 使用 WebSocket 实现手机控制端与电脑展示端的实时通信,支持断线重连、状态同步和双向数据交互。(最优方案)
  • 快递鸟 MCP Server:AI 工具解锁 物流 API 能力,开启智能物流新生态
  • UV Python 包和项目管理工具
  • 使用 Quill 实现编辑器功能
  • 企业网站建设的可行性图片编辑软件加文字
  • 零基础网站建设视频教程做淘宝美工的网站
  • 微米级光斑分析仪市场报告:政策、趋势与前景深度解析
  • 达梦 DM Database 集群:从概念到开发场景
  • 面向社科研究者:用深度学习做因果推断(一)
  • 站长seo计费系统比较好的网页模板网站
  • 【学习笔记】大模型
  • ES7243E 模拟音频转I2S输入给BES I2S_Master数据运行流程分析
  • 虚拟内存与RAM
  • 广州花都区网站建设长沙seo优化排名推广
  • 广告公司网站模版做一家网站要多少钱
  • 【Linux知识】Linux文本操作相关命令行
  • Port设置功能开发实践: Pyside6 MVC架构与Model/View/Delegate模式的应用
  • 白之家低成本做网站深圳比较好的建网站公司
  • 深度学习一些知识点(指标+正则化)
  • 企业官方网站建设的作用仿牌 镜像网站
  • java实现多线程分片下载超大文件,支持HTTPS。
  • 数据结构和算法(十)--B树
  • 从零起步学习MySQL || 第九章:从数据页的角度看B+树及MySQL中数据的底层存储原理(结合常见面试题深度解析)
  • HTTP 与 SOCKS5 代理协议:企业级选型指南与工程化实践
  • 新华三H3CNE网络工程师认证—STP状态机与收敛过程
  • 从零起步学习MySQL || 第十章:深入了解B+树及B+树的性能优势(结合底层数据结构与数据库设计深度解析)
  • 阿里云服务器网站备案台州北京网站建设
  • 眼镜网站建设深圳网站设计精选刻
  • CF1060 CD