数据结构(严蔚敏版)——线性表(二)【线性表的链式存储】

本文深入讲解了链表的基本概念及其实现方式,包括单链表、循环链表和双向链表的特点与操作方法,对比了不同链表类型的优劣。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据结构(严蔚敏版)第一章——复数的实现

数据结构(严蔚敏版)第二章 — 线性表(一)【单链表的顺序表示】

2.4、线性表的链式存储表示与实现

结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻

线性表的链式表示又称为非顺序映像或链式映像

链式存储结构特点:

  • 用一组物理位置任意的存储单元来存放线性表的数据元素
  • 这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的
  • 链表中元素的逻辑次序和物理次序不一定相同

结点的组成:数据域、指针域

2.4.1、与链式存储有关的术语

  1. **结点:**数据元素的存储映像。由数据域和指针域两部分组成
  2. **链表:**n个结点由指针链组成一个链表。
  3. 单链表:结点只有一个指针域的链表,称为单链表或线性表
  4. **双链表:**结点由两个指针域的链表,称为双链表
  5. **循环链表:**首尾相接的链表称为循环链表
  6. **头指针:**是指向链表中第一个结点的指针
  7. **首元结点:**是指链表中存储第一个数据元素 a 1 a_1 a1的结点
  8. **头结点:**是在链表的首元结点之前附设的一个结点

单链表的分类:

  • 带头节点
  • 不带头节点

2.4.2、单链表的表示

1、存储结构
typedef struct Lnode {			// 声明结点的类型和指针结点的指针类型
  ElemType data;						// 结点的数据域
  struct Lnode *next;				// 结点的指针域
}Lnode, *LinkList;					// LinkList为指向结构体Lnode的指针类型

定义链表L: LinkList L;

定义结点指针P: LNode *P 等同于LinkList p;

存储学生学号、姓名成绩的单链表表示:

typedef struct {
  char num[8];				// 数据域
  char num[8];				// 数据域
  int score    				// 数据域
} ElemType;

typedef struct Lnode {
  ElemType data;						// 数据域
  struct Lnode *next;				// 指针域
} Lnode, *LinkList;

2.4.3、单链表基本操作的实现

1、单链表的初始化(带头结点的单链表)
  • 生成新结点作为头结点,用头指针L指向头结点
  • 头结点的指针域为空
Status InitList(LinkList &L)
{ // 构造一个空的单链表
  L = new LNode;						// 生成新结点作为头结点,用头指针L指向头结点
  // L = (LinkList) malloc (sizeof(LNode));
  L -> next = NULL;					// 头结点的指针域为空
  return OK;
}
2、判断链表是否为空

空表:链表中五元素,称为空链表(头指针和头结点仍然在)

  • 判断指针域是否为空
int ListEmpty(LinkList L) 	// 若L为空表,则返回1,否则返回0
{
  if (L -> next )  					// 非空
    return 0;
  else 
    return 1;
}
3、单链表的销毁(销毁后链表不存在)
  • 从头指针开始,依次释放所有结点

image-20220924233512857

Status DestroyList(LinkList &L) 			// 销毁单链表L
{
  Lnode *p;															// 或LinkList p;
  while (L) {
    p = L;
    L = L -> next;
    delete p;
  }
  return OK;
}
3、清空单链表

链表仍存在,但链表中无元素,成为空链表(头指针和头结点仍然在)

  • 依次释放所有结点,并将头结点指针与设置为空

image-20220924234759060

Status ClearList(LinkList &L) {		// 将L重置为空表
  Lnode *p, *q;										// 或LinkList p,q;
  P = L -> next;
  while (p) {											// 没到表尾
    q = p -> next;
    delete p;
    p = q;
  }
  L -> next = NULL;								// 头结点指针域 
  return OK;
  
}
4、求单链表的表长
  • 从首元结点开始,依次计数所有结点

image-20220925094016564

int ListLength(LinkList L) {		// 返回L中数据元素个数
  LinkList *p;
  p = L -> next;									// p指向第一个结点
  i = 0;
  while (p) {
    i++;													// 遍历单链表,统计结点数
    p = p -> next;
  }
  return i;
}

image-20220925211651549

5、单链表的取值
  • 用指针p指向首元结点,用j做计数器初值赋为1

  • 从首元结点开始依次开始顺着链域next向下访问,只要指向当前结点的指针P不为空(NULL),并且没有达到序号为i结点,则循环执行以下操作:

    • p指向下一个结点
    • 计数器j相应加1
  • 退出循环时,如果指针p为空,或者计数器j 大于i,说明指定的序号i值不和法(i大于表长n或i小于等于0)取值失败返回ERROR;否则取值成功,此时j = i时,p所指的结点就是要找到的第i个结点,用参数e保存当前结点的数据域,返回OK。

Status GetElem(LinkList L,int i, ElemType &e)
{// 在带头结点的单链表L中根据序号i获取元素的值,用e返回L中第i个数据元素的值
 	p = L -> next; j = 1;									// 初始化,p指向首元结点,计数器j初值赋为1
  while (p && j < 1) {									// 顺链域向后扫描,直到p为空或p指向第i个元素
    p = p -> next;											// p指向下一个结点
    ++j;																// 计数器j相应加1    
  }
  if (!p || j > i) return ERROR;				// i值不合法i > n或i <= 0
  e = p -> data;												// 取第i个结点的数据域
  return OK;
}
6、单链表的按值查找
  • 用指针p指向首元结点
  • 从首元结点开始依次顺着链域next向下查找,只要指向当前结点的指针p不为空,并且p所指结点的数据域不等于给定值e,则循环执行以下操作:p指向下一个结点
  • 返回p。若查找成功,p此时即为结点的地址值,若查找失败,p的值即为NULL

根据指定数据获取该元素数据的地址:

LNode *LocateElem(LinkList L, ElemType e)
{// 在带头结点的单链表L中查找值为e的元素
  p = L -> next;									// 初始化,p指向首元结点
  while (p && p -> data != e)		  // 顺链域向后扫描,直到p为空或p所指结点的数据域等于e
    p = p -> next;								// p指向下一个结点
  return p;												// 查找成功返回值e的结点地址p,查找失败p为NULL
}

根据指定数据获取该数据位置序号:

int LocateElem(LinkList L, ElemType e) 
{//返回L中值为e的数据元素的位置序号,查找失败返回0
  p = L -> next; j = 1;
  while (p && p -> data != e) {
    p = p -> next ;
    j++;
  }
  if (p) return j;
  else return 0;
}
7、单链表的插入
  • 将值为e的新结点插入到表的第i个结点的位置上,即插入到结点ai-1与ai之间
  • 查找结点ai-1并由指针p指向该结点。
  • 生成一个新结点*s
  • 将新结点*s的数据域置为e,s -> data = e;
  • 将新结点*s的指针域指向结点ai, s -> next = p -> next;
  • 将结点*p的指针域指向新结点*s,p -> next = s;
Status ListInsert(LinkList L, int i, ElemType e)
{// 在带头结点的单链表L中第i个位置插入值为e的新结点
  p = L; j = 0;
  while (p && (j < i - 1)) {
    p = p -> next;													// 查找第i - 1个结点,p指向该结点
    ++j;
  }
  if (!p || j > i - 1) return ERROR;				// i > n+1 或者 i < 1
  s = new LNode;														// 生成新结点*s
  s -> data = e;														// 将结点*s的数据域置为e
  s -> next = p -> next;										// 将结点*s的指针域指向结点ai
  p -> next = s;														// 将结点*p的指针域指向结点*s
  return OK;
}
8、单链表的删除

【算法步骤】:

  • 删除单链表的第i个结点ai
  • 查找结点ai-1并由指针p指向该结点
  • 临时保存待删除结点ai的地址在q中,以备释放
  • 将结点*p的指针域指向ai的直接后继结点
  • 释放结点ai的空间
Status ListDelete(LinkList &L, int i)
{// 在带头结点的单链表L中,删除第i个元素
  p = L; j = 0;
  while ((p -> next) && (j < i - 1)) {
    p = p > next;												// 查找第i - 1个结点,p指向该结点
    ++j;
  }
  if (!(p -> next) || (j > i - 1)) return ERROR;		// 当i > n 或 i < 1时删除位置不合理
  q = p -> next;																		// 临时保存被删除结点的地址以备释放
  p -> next = p -> next -> next;										// 改变删除结点前驱结点的指针域
  delete q;																					// 释放删除结点空间
  return OK;
}
9、单链表的建立(头插法)
  • 创建一个只有头结点的空链表
  • 根据带创建链表包括的元素个数n,循环n次执行以下操作:
    • 生成一个新结点*p;
    • 输入元素赋值给新结点*p的数据域
    • 将新结点*p插入到头结点之后

image-20220926081750621

void CreateList_H(LinkList &L, int n)
{// 逆序位输入n个元素值,建立带头结点的单链表L
  L = new LNode;
  L -> next = NULL;										// 先建立一个带头结点的空链表
  for (i = n; i > 0; --i)
  {
    p = new LNode;										// 生成新结点*p
    cin >> p -> data;									// 输入元素赋给新结点*p的数据域
    p -> next = L -> next;
    L -> next = p;										// 将新结点*p插入到头结点之后
  }
}
4、单链表的建立(尾插法)
  • 创建一个只有头结点的空链表
  • 尾指针r初始化,指向头结点
  • 根据创建链表包括的元素个数n,循环n次执行以下操作:
    • 生成一个新结点*p
    • 输入元素值赋给新结点*p的数据域
    • 将新结点*p插入到尾结点*r之后
    • 尾指针r指向新的尾结点*p
void CretaeList_R(LinkList &L, int n)
{// 正位序输入n个元素值,建立带头结点的单链表L
  L = new LNode;
  L->next = NULL;															// 先建立一个带头结点的空链表
  r = L;																			// 尾指针r指向头结点
  for (i = 0; i < n; ++i)
  {
    p = new LNode;														// 生成新结点
    cin >> p -> data;													// 输入元素值赋给新结点*p的数据域
    p -> next = NULL; r ->next = p;						// 将新结点*p插入尾结点*r之后
    r = p;																		// r指向新的尾结点
  }
}

2.4.4、循环链表

**循环链表:**是一种头尾相接的链表(即:表中最后一个结点的指针域指向头结点,整个链表形成一个环)

注意:

  • 由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件不再是p或p->next是否为空而是他们是否等于头指针

  • 循环条件

    image-20220926154555415

  • 头指针表示单循环链表

    • 找a1的时间复杂度: O ( 1 ) O(1) O(1)
    • 找an的时间复杂度: O ( n ) O(n) O(n)

注意:表的操作常常是在表的首位位置上进行

  • 尾指针表示单循环链表
    • a1的存储位置是:R -> next -> next
    • an的存储位置是:R
  • 尾指针循环链表的合并(将Tb合并在Ta之后)
p =Ta -> next;
Ta -> next = Tb -> next -> next;
delete Tb -> next;
Tb -> next = p;

image-20220926160814163

【算法描述】:

LinkList Connect(LinkList Ta, LinkList Tb)
{// 假设Ta、Tb都是非空的单循环链表
  p = Ta -> next;													// p存表头结点
  Ta -> next = Tb -> next -> next;				// Tb表头连结Ta表尾
  delete Tb -> next;											// 释放Tb表头结点
  Tb -> next = p;													// 修改指针
  return Tb;
}

2.4.5、双向链表

1、双向链表的结构定义如下:
typedef struct DuLNode {
  ElemType data;									// 数据域
  struct DuLNode *prior;					// 前驱指针
  struct DuLNode *next;						// 后继指针
}DuLNode, *DuLinkList;
2、双向链表的插入

image-20220926164041286

Status ListInsert_DuL(DuLinkList &L, int i, ElemType e)
{// 在带头结点的双向循环链表L中第i个位置之前插入元素e
  if (!(p = GetElemP_DuL(L,i))) return ERROR;
  s = new DuLNode;
  s -> data = e;
  s -> prior = p -> prior;
  p -> prior -> next = s;
  s -> next = p;
  p -> prior = s;
  return OK;
}
3、双向链表的删除

image-20220926165219182

算法描述:

Status ListDelete_DuL(DuLinkList &L, int i)
{// 删除带头结点的双向链表L中的第i个元素
  if (!(p=GetElem_DuL(L,i)))							// 在L中确定第i个元素的位置指针p
    return ERROR;													// p为NULL时,第i个元素不存在
  p -> prior -> next = p -> next;					// 修改被删结点的前驱结点的后继指针
  p -> next -> prior = p -> prior;				// 修改被删除结点的后继结点的前驱指针
  delete p;																// 释放被删结点的空间
  return OK;
}
单链表、循环链表和双向链表的时间效率比较
查找表头结点
(首元节点)
查找表尾结点查找结点*p的前驱结点
带头结点的单链表LL ->next
时间复杂度 O ( 1 ) O(1) O(1)
从L -> next 依次向后遍历
时间复杂度 O ( n ) O(n) O(n)
通过p -> next无法找到其前驱
带头结点仅设尾指针L的循环单链表L ->next
时间复杂度 O ( 1 ) O(1) O(1)
从L -> next 依次向后遍历
时间复杂度 O ( n ) O(n) O(n)
通过p -> next 可以找到其前驱时间复杂度 O ( n ) O(n) O(n)
带头结点仅设尾指针R的循环单链表R ->next
时间复杂度 O ( 1 ) O(1) O(1)
R
时间复杂度 O ( 1 ) O(1) O(1)
通过p -> next 可以找到其前驱时间复杂度 O ( n ) O(n) O(n)
带头结点的双向循环链表LL ->next
时间复杂度 O ( 1 ) O(1) O(1)
L -> prior
时间复杂度 O ( 1 ) O(1) O(1)
p -> prior
时间复杂度 O ( 1 ) O(1) O(1)

2.5、顺序表和链表的比较

链式存储结构的优点

  • 结点空间可以动态申请和释放
  • 数据元素的逻辑次序靠结点的指针来指示,即插入和删除时不需要移动数据元素。

链式存储结构的缺点

  • 存储密度小,每个结点的指针域需要额外占用存储空间

  • 链式存储结构是非随机存储结构。对任一结点的操作都要从头指针依指针链查找到该结点

  • 存储密度 = 结点数据本身占用的空间 ÷ 结点占用的空间总量 结点数据本身占用的空间\div 结点占用的空间总量 结点数据本身占用的空间÷结点占用的空间总量

顺序表域链表的比较

image-20220926224610161

数据结构》(C语言)是为“数据结构”课程编写的教材,也可作为学习数据结构及其算法的C程序设计的参数教材。学了数据结构后,许多以前写起来很繁杂的代码现在写起来很清晰明了. 本书的前半部分从抽象数据类型的角度讨论各种基本类型的数据结构及其应用;后半部分主要讨论查找和排序的各种实现方法及其综合分析比较。 全书采用类C语言作为数据结构和算法的描述语言。 本书概念表述严谨,逻辑推理严密,语言精炼,用词达意,并有配套出的《数据结构题集》(C语言),便于教学,又便于自学。 本书后附有光盘。光盘内容可在DOS环境下运行的以类C语言描述的“数据结构算法动态模拟辅助教学软件,以及在Windows环境下运行的以类PASCAL或类C两种语言描述的“数据结构算法动态模拟辅助教学软件”。内附 数据结构算法实现(严蔚敏配套实现程序) 目录: 第1章 绪论 1.1 什么是数据结构 1.2 基本概念和术语 1.3 抽象数据类型的表现与实现 1.4 算法和算法分析 第2章 线性表 2.1 线性表的类型定义 2.2 线性表的顺序表示和实现 2.3 线性表链式表示和实现 2.4 一元多项式的表示及相加 第3章 栈和队列 3.1 栈 3.2 栈的应有和举例 3.3 栈与递归的实现 3.4 队列 3.5 离散事件模拟 第4章 串 4.1 串类型的定义 4.2 串的表示和实现 4.3 串的模式匹配算法 4.4 串操作应用举例 第5章 数组和广义表 5.1 数组的定义 5.2 数组的顺序表现和实现 5.3 矩阵的压缩存储 5.4 广义表的定义 5.5 广义表的储存结构 5.6 m元多项式的表示 5.7 广义表的递归算法第6章 树和叉树 6.1 树的定义和基本术语 6.2 叉树 6.2.1 叉树的定义 6.2.2 叉树的性质 6.2.3 叉树的存储结构 6.3 遍历叉树和线索叉树 6.3.1 遍历叉树 6.3.2 线索叉树 6.4 树和森林 6.4.1 树的存储结构 6.4.2 森林与叉树的转换 6.4.3 树和森林的遍历 6.5 树与等价问题 6.6 赫夫曼树及其应用 6.6.1 最优叉树(赫夫曼树) 6.6.2 赫夫曼编码 6.7 回溯法与树的遍历 6.8 树的计数 第7章 图 7.1 图的定义和术语 7.2 图的存储结构 7.2.1 数组表示法 7.2.2 邻接表 7.2.3 十字链表 7.2.4 邻接多重表 7.3 图的遍历 7.3.1 深度优先搜索 7.3.2 广度优先搜索 7.4 图的连通性问题 7.4.1 无向图的连通分量和生成树 7.4.2 有向图的强连通分量 7.4.3 最小生成树 7.4.4 关节点和重连通分量 7.5 有向无环图及其应用 7.5.1 拓扑排序 7.5.2 关键路径 7.6 最短路径 7.6.1 从某个源点到其余各顶点的最短路径 7.6.2 每一对顶点之间的最短路径 第8章 动态存储管理 8.1 概述 8.2 可利用空间表及分配方法 8.3 边界标识法 8.3.1 可利用空间表的结构 8.3.2 分配算法 8.3.3 回收算法 8.4 伙伴系统 8.4.1 可利用空间表的结构 8.4.2 分配算法 8.4.3 回收算法 8.5 无用单元收集 8.6 存储紧缩 第9章 查找 9.1 静态查找表 9.1.1 顺序表的查找 9.1.2 有序表的查找 9.1.3 静态树表的查找 9.1.4 索引顺序表的查找 9.2 动态查找表 9.2.1 叉排序树和平衡叉树 9.2.2 B树和B+树 9.2.3 键树 9.3 哈希表 9.3.1 什么是哈希表 9.3.2 哈希函数的构造方法 9.3.3 处理冲突的方法 9.3.4 哈希表的查找及其分析 第10章 内部排序 10.1 概述 10.2 插入排序 10.2.1 直接插入排序 10.2.2 其他插入排序 10.2.3 希尔排序 10.3 快速排序 10.4 选择排序 10.4.1 简单选择排序 10.4.2 树形选择排序 10.4.3 堆排序 10.5 归并排序 10.6 基数排序 10.6.1 多关键字的排序 10.6.2 链式基数排序 10.7 各种内部排序方法的比较讨论 第11章 外部排序 11.1 外存信息的存取 11.2 外部排序的方法 11.3 多路平衡归并的实现 11.4 置换一选择排序 11.5 最佳归并树 第12章 文件 12.1 有关文件的基本概念 12.2 顺序文件 12.3 索引文件 12.4 ISAM文件和VSAM文件 12.4.1 ISAM文件 12.4.2 VSAM文件 12.5 直接存取文件(散列文件) 12.6 多关键字文件 12.6.1 多重表文件 12.6.2 倒排文件 附录A 名词索引 附录B 函数索引 参考书目
1.1 数组和字符串 2 1.1.1 一维数组的倒置 2 范例1-1 一维数组的倒置 2 ∷相关函数:fun函数 1.1.2 一维数组应用 3 范例1-2 一维数组应用 3 1.1.3 一维数组的高级应用 5 范例1-3 一维数组的高级应用 5 1.1.4 显示杨辉三角 7 范例1-4 显示杨辉三角 7 ∷相关函数:c函数 8 1.1.5 魔方阵 9 范例1-5 魔方阵 9 1.1.6 三维数组的表示 14 范例1-6 三维数组的表示 14 ∷相关函数:InitArray函数 1.1.7 多项式的数组表示 17 范例1-7 多项式数组的表示 17 1.1.8 查找矩阵的马鞍点 19 范例1-8 查找矩阵的马鞍点 19 ∷相关函数:Get_Saddle函数 1.1.9 对角矩阵建立 21 范例1-9 对角矩阵建立 21 ∷相关函数:Store函数 1.1.10 三对角矩阵的建立 22 范例1-10 三对角矩阵的建立 22 ∷相关函数:Store函数 1.1.11 三角矩阵建立 24 范例1-11 三角矩阵建立 24 ∷相关函数:Store函数 1.1.12 对称矩阵的建立 25 范例1-12 对称矩阵的建立 25 ∷相关函数:store函数 1.1.13 字符串长度的计算 28 范例1-13 字符串长度的计算 28 ∷相关函数:strlen函数 1.1.14 字符串的复制 29 范例1-14 字符串的复制 29 ∷相关函数:strcpy函数 1.1.15 字符串的替换 31 范例1-15 字符串的替换 31 ∷相关函数:strrep函数 1.1.16 字符串的删除 33 范例1-16 字符串的删除 33 ∷相关函数:strdel函数 1.1.17 字符串的比较 35 范例1-17 字符串的比较 35 ∷相关函数:strcmp函数 1.1.18 字符串的抽取 36 范例1-18 字符串的抽取 36 ∷相关函数:substr函数 1.1.19 字符串的分割 38 范例1-19 字符串的分割 38 ∷相关函数:partition函数 1.1.20 字符串的插入 40 范例1-20 字符串的插入 40 ∷相关函数:insert函数 1.1.21 字符串的匹配 42 范例1-21 字符串的匹配 42 ∷相关函数:nfind函数 1.1.22 字符串的合并 43 范例1-22 字符串的合并 43 ∷相关函数:catstr函数 1.1.23 文本编辑 45 范例1-23 文本编辑 45 ∷相关函数:StrAssign函数 1.2 栈和队列 54 1.2.1 用数组仿真堆栈 54 范例1-24 用数组仿真堆栈 54 ∷相关函数:push函数 pop函数 1.2.2 用链表仿真堆栈 57 范例1-25 用链表仿真堆栈 57 ∷相关函数:push函数 pop函数 1.2.3 顺序栈公用 59 范例1-26 顺序栈公用 59 ∷相关函数:push函数 pop函数 1.2.4 进制转换问题 61 范例1-27 进制转换问题 61 ∷相关函数:MultiBaseOutput函数 1.2.5 顺序队列操作 64 范例1-28 顺序队列操作 64 ∷相关函数:push函数 pop函数 1.2.6 循环队列 66 范例1-29 循环队列 66 ∷相关函数:EnQueue函数 DeQueue函数 1.2.7 链队列的入队、出队 69 范例1-30 链队列入队、出队 69 ∷相关函数:push函数 pop函数 1.2.8 舞伴问题 71 范例1-31 舞伴问题 71 ∷相关函数:EnQueue函数 DeQueue函数 DancePartner函数 1.3 链表 75 1.3.1 头插法建立单链表 75 范例1-32 头插法建立单链表 75 ∷相关函数:createlist函数 1.3.2 限制链表长度建立单链表 77 范例1-33 限制链表长度建立长单链表 77 ∷相关函数:createlist函数 1.3.3 尾插法建立单链表 79 范例1-34 尾插法建立单链表 79 ∷相关函数:createlist函数 1.3.4 按序号查找单链表 80 范例1-35 按序号查找单链表 80 ∷相关函数:getnode函数 1.3.5 按值查找单链表 82 范例1-36 按值查找单链表 82 ∷相关函数:locatenode函数 1.3.6 链表的插入 84 范例1-37 链表的插入 84 ∷相关函数:insertnode函数 1.3.7 链表的删除 86 范例1-38 链表的删除 86 ∷相关函数:deletelist函数 1.3.8 归并两个单链表 88 范例1-39 归并两个单链表 88 ∷相关函数:concatenate函数 1.3.9 动态堆栈 90 范例1-40
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java小豪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值