数据结构算法第三章 线性表
为巽1.线性表的定义
线性表:零个或多个数据元素的有限序列。
它是一个序列。如果有多个元素,则第一个元素无前驱,最后一个元素无后继,其他的每一个元素只有一个前驱和后继。
将线性表记为 a1,a2,a3,则a1是a2的直接前驱元素,a3是a2的直接后继元素。有且仅仅只有一个直接前驱和一个直接后继。
所以线性表元素的个数n(n≥0)定义为线性表的长度,当n=0,为空表。
在非空表中的每个元素都有一个确定的位置,比如a1是第一个,a3是最后一个。a2是第二个,称2为数据元素a2在线性表中的位序。
2.线性表的抽象数据类型
当传递一个参数给函数的时候,这个参数会不会在函数内被改动决定了使用什么参数形式。
如果需要被改动,则需要传递指向这个参数的指针。
如果不用被改动可以直接传递这个参数。
总结:需要改动则传递参数的指针,不需要则传递这个参数。
3.线性表的顺序存储结构
顺序存储结构
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
说白了就是:在内存中,占个地方,通过占位的形式,将一定的内存空间给占,然后把相同数据类型的数据元素一次存放在这里。
使用一维数组来实现顺序存储结构

数据长度与线性表长度的区别
数组的长度是存放线性表的存储空间长度。
线性表的长度是线性表中数据元素的个数,会变化。
地址计算方法
C语言中的数组是从0开始为第一个下标。所以线性表的第i个元素是要存储在数组下标为i-1的位置。
存储器中的每个存储单元都有自己的编号,这个编号为地址。
假设占用的是c个存储单元,那么线性表中第i+1个数据元素的存储位置和第i个数据元素的存储位置满足以下关系(LOC表示获得存储位置的函数)

例如:如果LOC(1)为第一个元素的地址,每个元素占用4个存储单元,即C=4,那么第二个元素LOC(2),就是LOC(1)+4
所以对于第i个数据元素ai的存储位置可以得出ai:


4.顺序存储结构的插入与删除
获得元素操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| // 定义两个宏,分别表示函数执行成功和失败的状态 #define OK 1 #define ERROR 0
// 定义一个新的类型名Status,它实际上就是int类型 typedef int Status;
// 函数声明:从顺序表L中获取第i个位置的元素,并将其赋值给变量e // SqList L:顺序表结构体 // int i:表示获取元素的位置,也就是索引 // ElemType *e:这是一个指针,用于存放获取到的元素。 Status GetElem(SqList L, int i, ElemType *e) { // 检查i是否在合法范围内,即i大于0且小于等于顺序表的长度 if (i < 1 || i > L.length) { // 如果i不合法,返回错误状态ERROR return ERROR; } // 如果i合法,将第i个位置的元素赋值给e指向的变量 // 注意:由于数组索引是从0开始的,所以第i个元素实际上是L.data[i-1] *e = L.data[i - 1]; // 返回成功状态OK return OK; }
|
插入操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| // 函数声明,用于在顺序表L中第i个位置插入元素e,并返回操作状态 Status ListInsert(SqList *L, int i, ElemType e) { int k;
// 检查顺序表是否已满,如果已满,返回错误状态 if (L->length == MAXSIZE) return ERROR;
// 检查插入位置i是否合法,如果不合法(小于1或大于顺序表长度+1),返回错误状态 if (i < 1 || i > L->length + 1) return ERROR;
// 如果插入位置不在表尾 if (i <= L->length) { // 从最后一个元素开始,直到要插入的位置,将每个元素向后移动一位 for (k = L->length - 1; k >= i - 1; k--) { // 将元素向后移动 L->data[k + 1] = L->data[k]; } }
// 将新元素e插入到位置i-1(因为数组索引从0开始) L->data[i - 1] = e;
// 顺序表长度增加1 L->length++;
// 插入成功,返回成功状态 return OK; }
|
删除操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| // 函数声明,从顺序表L中删除第i个位置的元素,并通过e返回该元素的值,函数返回操作状态 Status ListDelete(SqList *L, int i, ElemType *e){ int k;
// 检查顺序表是否为空,如果为空,返回错误状态 if(L->length == 0) return ERROR;
// 检查删除位置i是否正确,即i是否在1到L->length的范围内,如果不正确,返回错误状态 if(i < 1 || i > L->length) return ERROR;
// 将要删除的元素赋值给*e,即通过指针e返回被删除元素的值 *e = L->data[i-1];
// 如果删除的位置不是最后一个元素,则需要将后续元素前移 if(i < L->length){ for(k = i; k < L->length; k++){ // 将位置k的元素前移到位置k-1 L->data[k-1] = L->data[k]; } }
// 顺序表的长度减1,因为已经删除了一个元素 L->length--;
// 如果删除操作成功,返回OK return OK; }
|
线性表顺序存储结构的优缺点

5.线性表的链式存储结构
顺序存储结构不足的解决办法
缺点:插入和删除需要移动大量元素
线性表链式存储结构定义
特点:用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续,也可以不是连续。
存储数据元素信息的域称为数据域,存储直接后继位置的域称为指针域。
指针域存储的信息称为指针或链。这两部分信息组成数据元素ai的存储映像,为结点。
n个结点(ai的存储映像)链结成一个链表,即为线性表的链式存储结构,因此链表的每个节点只包含每个指针域,称为单链表。
单链表:通过每个节点的指针将线性表的数据元素按照逻辑次序链接在一起。

链表中第一个节点的存储位置叫做头指针,所以整个链表的存储从头指针开始。之后的每一个节点,就是上一个后继指针指向的位置。最后一个,直接后继不存储,所以线性链表的最后一个节点指针为“NULL”。

为了方便会在单链表的第一个节点前设一个头节点。

头指针与头节点的区别

线性表链式存储结构代码描述
若线性表为空,则头节点的指针域为空


带有头节点的单链表,如下

空链表如下

1 2 3 4 5 6 7
| // 定义链表节点结构体 typedef struct None { int data; // 数据域 struct None *next; // 指向下一个节点的指针 } Node; // 使用 Node 作为别名
typedef struct None *Link; // 定义指向 Node 的指针类型 Link
|
从代码中来看:
结点由存放数据元素的数据域,存放后继节点地址的指针域组成。

单链表的读取
算法思路:
(1)声明一个指针p指向链表的第一个结点,初始化j从1开始。
(2)当j<i时,遍历链表,让p的指针向后移动,不断指向下一结点,j累加i。
(3)若到链表末尾p为空,则说明第i个结点不存。
(4)否则查找成功,返回结点p的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| static GetElem(LinkList L, int i, ElemType *e) { int j; //声明结点p p = L->next; //让p指向链表L的第一个结点 j = 1; //j为计数器 while (p && j < i) //p不为空或计算器j还没有等于i时,循环继续 { p = p->next; //让p指向下一个结点 j++; } if (!p || j > i) { return ERROR; //第i个不存在 } *e = p->data; //读取第i个元素的数据 return OK; } // ||:一个为真,则都为真 &&:要两个都为真
|
单链表的插入与删除
插入算法思路
(1)声明一个指针p指向链表头结点,初始化j从1开始。
(2)当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
(3)若到链表末尾p为空,则说明第i个结点不存在。
(4)否则查找成功,在系统中生成一个空结点s。
(5)将数据元素e赋值给s->data。
(6)单链表的插入标准语句s->next = p->next; p->next = s;
(7)返回成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| static GetElem(LinkList L, int i, ElemType *e) { int j; LinkList p = L->next; // 假设 L 是头结点,p 指向第一个数据结点 j = 1; // 初始化计数器,从第 1 个结点开始 while (p && j < i) // 遍历链表,直到找到第 i 个结点 { p = p->next; j++; } if (!p || j > i) // 如果链表为空或者 j 超过了 i,返回错误 { return ERROR; } // 插入新结点 LinkList s = (LinkList)malloc(sizeof(Node)); // 为新结点分配内存 s->data = e; s->next = p->next; // 将p的后继结点赋值给s的后继 p->next = s; // 将s赋值给p的后继
return OK; }
|
删除算法思路
(1)声明一个指针p指向链表头结点,初始化j从1开始。
(2)当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
(3)若到链表末尾p为空,则说明第i个结点不存在。
(4)否则查找成功,将要删除的结点p->next赋值给q。
(5)单链表的删除标准语句p->next = q->next。
(6)将q结点中的数据赋值给e,作为返回
(7)释放q结点。
(8)返回成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| static GetElem(LinkList L, int i, ElemType *e) { int j; p = L->next; // 初始化 p 指针,指向链表的第一个节点 j = 1; // 从第 1 个节点开始计数 while (p && j < i) // 遍历链表,找到第 i 个节点的前一个节点 { p = p->next; j++; } if (!p || j > i) // 如果链表长度不足 i,或者 j 超过 i,返回错误 { return ERROR; } q = p->next; // 找到要删除的节点 q(即第 i 个节点) p->next = q->next; // 让 p 的后继指针跳过 q,指向 q 的后继节点 *e = q->data; // 将要删除的节点 q 的数据保存到 e 中 free(q); // 释放被删除节点 q 所占的内存 return OK; // 返回成功 }
|
单链表的整表创建
(1)声明一个指针p和计算器变量i。
(2)初始化一空链表L。
(3)让L的头结点的指针指向NULL,建立一个带头结点的单链表。
(4)循环。
1、生成一个新节点赋值给p。
2、随机生成一个数字赋值给p的数据域p->data。
3、将p插入到头结点与前一新结点之间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| static GetElem(LinkList L, int i, ElemType *e) { LinkList p; // 定义一个指针 p 用于遍历链表 int i; // 定义计数器 i(注意:与外部函数参数 i 冲突) srand(time(0)); // 初始化随机数种子,确保每次运行时生成不同的随机数 *L = (LinkList)malloc(sizeof(LNode)); // 为头节点分配内存 (*L)->next = NULL; // 将头节点的后继指针设为 NULL,表示链表初始化为空 for (i = 0; i < n; i++) // 循环生成 n 个节点 { p = (LinkList)malloc(sizeof(LNode)); // 为新节点 p 分配内存 p->data = rand() % 100 + 1; // 生成 1 到 100 之间的随机数,并赋给 p->data p->next = (*L)->next; // 将当前链表的第一个节点赋值为 p 的后继 (*L)->next = p; // 将 p 插入到头节点之后 } }
|
循环链表
循环链表和单链表的差异是p->netx是判断是否为空,循环链表的判断是p->next不等于头结点。

1 2 3 4 5 6
| P = rearA -> next; //保存A表的头结点,1点 rearA -> next = rearB -> next -> next; //将本是指向B表的第一个结点(不是头结点)
q = rearB -> next; rearB -> next = p; //赋值给rearA->next,2点 free(q); //释放q
|
双向链表
双向链表:是在单链表的每个节点中,在设置一个指向前驱结点的指针域。
1 2 3 4 5 6
| typedef struct DulNode { ElemType data; struct DuLNode *prior; //直接前驱指针 struct DuLNone *next; //直接后继指针 }DulNode,*DuLinkList;
|
总结


