基础知识
单链表有两种实现方式,带头结点和不带头结点
每个结点除了存放数据元素外,还要存储指向下一个结点的指针
顺序表优点:可随机存储,存储密度高
缺点:要求大片连续空间,改变容量不方便
单链表优点:不要求大片连续空间,改变容量方便
缺点:不可随机存取,要耗费一定空间存放指针
前提代码
#include <iostream>
using namespace std;
#define MAXSIZE 20 // 存储空间初始分配量
struct Node //将用于表示链表中的节点
{
int data; //每个结点存放一个数据元素
Node* next; //表示指向下一个节点的指针
};
typedef Node* LinkList; //Node*表示指向Node结构体的指针类型,而LinkList是用来代表这个指针类型的别名。
//为了提高程序的可读性,在此对同一结构体指针类型起了两个名称,LinkList 与 LNode*,
//两者本质上是等价的。通常习惯上用 LinkList 定义单链表,强调定义的是某个单链表的头指针;
//用 LNode* 定义指向单链表中任意结点的指针变量。例如,若定义 LinkList L,则 L 为单链表的头指针,
//若定义 LNode* p,则 p 为指向单链表中某个结点的指针,用 *p 代表该结点。
//当然也可以使用定义 LinkList p,这种定义形式完全等价于 LNode* p。
打印整数
bool visit(int c)
{
cout << c;
return true;
}
初始化链式线性表
bool InitList(LinkList *L)
{
//*L=(LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
*L = new Node; //用于创建一个新的节点,并将该节点的地址赋值给指针变量*L,即链表的头节点指针。
if (!(*L)) /* 存储分配失败 */
return 0;
(*L)->next = NULL; /* 指针域为空 */
return true;
}
//if (!(*L)) 检查 *L 是否为 NULL。如果 *L 是 NULL,那么 !(*L) 就为 true,if 语句中的代码块就会被执行。如果 *L 不是 NULL,那么 !(*L) 就为 false,if 语句中的代码块就不会被执行。
带头结点更方便
检查是否空表
#include <iostream>
using namespace std;
struct Node
{
int data;
Node* next;
};
typedef Node* LinkList; // 定义链表类型
bool InitList(LinkList* L)
{
*L = new Node;
if (!(*L))
return false;
(*L)->next = nullptr;
return true;
}
bool ListEmpty(LinkList L)
{
if (L->next)
return false;
else
return true;
}
int main()
{
LinkList L;
InitList(&L);
if (ListEmpty(L))
cout << "The list is empty." << endl;
else
cout << "The list is not empty." << endl;
return 0;
}
重置空表
bool ClearList(LinkList* L) //是一个指向 LinkList 类型的指针的指针。这意味着 L 指向一个指针,而这个指针指向链表的头结点。
{
LinkList p, q; //声明了两个 LinkList 类型的指针,p 和 q。
p = (*L)->next; //*L 表示对指针 L 所指向的对象进行解引用。如果 L 是一个指针,那么 *L 就是这个指针指向的实际对象。
while (p) //这个循环条件表示只要p指向的节点不为空,即链表还有节点需要释放,就继续执行循环。
{
q = p->next; //将下一个节点的指针保存到临时指针变量q中,以便在释放当前节点后能够访问到下一个节点。
delete p; // 使用delete操作符释放当前节点p的内存空间,即释放节点所占用的内存。
p = q; //将指针p移动到下一个节点,即将p指向q所指向的节点,以便在下一次循环中处理下一个节点。
}//通过这个循环,可以逐个释放链表中的节点,直到链表为空
(*L)->next = NULL; // 头节点的指针域置为空
return true; // 返回操作成功
}
//p和q是临时指针变量,用于遍历链表节点。
//p指向链表的第一个节点,即头节点的下一个节点。
//使用while循环遍历链表,直到遍历到链表尾部,即p为NULL。
//在循环中,首先将下一个节点的指针保存到q中,然后使用delete释放当前节点p的内存空间。
返回L中数据元素个数
int ListLength(LinkList L)
{
int i = 0;
LinkList p = L->next; /* p指向第一个结点 */
while(p)
{
i++;
p = p->next;
}
return i;
}
用e返回L中第i个数据元素的值(按位查找)
bool GetElem(LinkList L,int i,int *e)
//在函数GetElem中,L作为参数传入,它代表了整个链表。由于链表的操作通常都是从链表的头部开始的,
//所以L一开始就指向头结点,这样设计是为了后续操作的便利。
{
int j;
LinkList p; /* 声明一结点p */
p = L->next; /* 让p指向链表L的第一个结点 */
j = 1; /* j为计数器 */
while (p && j<i) /* p不为空或者计数器j还没有等于i时,循环继续 */
{
j++;
p = p->next; /* 让p指向下一个结点 */
}
if ( !p || j>i )
return 0; /* 第i个元素不存在 */
*e = p->data; /* 取第i个元素的数据 */
return true;
}
返回L中第1个与e满足关系的数据元素的位序(按值查找)
#include <iostream>
using namespace std;
struct LNode
{
int data;
LNode* next;
};
int LocateElem(LNode* L, int e)
{
int i = 0;
LNode* p = L->next;
while (p)
{
i++;
if (p->data == e) /* 找到这样的数据元素 */
return i;
p = p->next;
}
return 0; // 未找到指定元素,返回0
}
int main()
{
// 创建链表
LNode* L = new LNode;
L->next = nullptr;
// 在链表中插入一些元素
for (int i = 1; i <= 5; i++)
{
LNode* newNode = new LNode;
newNode->data = i;
newNode->next = L->next;
L->next = newNode;
}
// 查找元素位置并输出结果
int position = LocateElem(L, 3);
if (position != 0)
cout << "元素3的位置是:" << position << endl;
else
cout << "未找到元素3" << endl;
// 释放链表节点的内存
LNode* p = L;
while (p)
{
LNode* temp = p;
p = p->next;
delete temp;
}
return 0;
}
在L中第i个位置之后插入新的数据元素e,L的长度加1
bool ListInsert(LinkList *L,int i,int e)
{
int j;
LinkList p,s;
p = *L;
j = 1;
//将指向链表头节点的指针 *L 赋值给指针 p。这样做是为了在后续的代码中使用指针 p 来遍历链表。
while (p && j < i) /* 寻找第i个结点 */
{
p = p->next;
++j;
}
if (!p || j > i)
return 0; /* 第i个元素不存在 */
//s = (LinkList)malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */
s = new Node; //变量s是一个指向链表节点的指针,用于表示要插入的新节点
//创建了一个新的 Node 类型的节点,并将分配的内存地址赋值给指针 s
s->data = e;
s->next = p->next; //为了将新节点插入到当前节点p之后
p->next = s; //为了将新节点插入到链表中,使其成为当前节点p的后继节点。
return true;
}
前插就是后插再交换
删除L的第i个数据元素,并用e返回其值,L的长度减1
bool ListDelete(LinkList *L,int i,int *e)
{
int j;
LinkList p,q;
p = *L;
j = 1;
while (p->next && j < i) /* 遍历寻找第i个元素 */
{
p = p->next;
++j;
}
if (!(p->next) || j > i)
return false; /* 第i个元素不存在 */
q = p->next; //保存要删除的节点,以便后续操作。
p->next = q->next; //跳过节点 q,将链表重新连接起来,实现了删除节点的操作。
//在链表中,每个节点都有一个指针 next 指向下一个节点。当我们要删除节点 q 时,
//我们需要将 q 的前一个节点的 next 指针指向 q 的后一个节点,即跳过节点 q。
*e = q->data; /* 将q结点中的数据给e */
free(q); /* 让系统回收此结点,释放内存 */
return true;
}
依次对L的每个数据元素输出
bool ListTraverse(LinkList L)
{
LinkList p = L->next;
while(p)
{
visit(p->data);
p = p->next;
}
cout<<endl;
return 1;
}
随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)
void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;
srand(time(0)); // 初始化随机数种子
//*L = (LinkList)malloc(sizeof(Node));
*L = new Node;
(*L)->next = NULL; // 先建立一个带头结点的单链表
for (i = 0; i < n; i++)
{
//p = (LinkList)malloc(sizeof(Node)); // 生成新结点
p = new Node;
p->data = rand()%100+1; // 随机生成100以内的数字
p->next = (*L)->next;
(*L)->next = p; // 插入到表头
}
}
随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)
void CreateListTail(LinkList *L, int n)
{
LinkList p,r;
int i;
srand(time(0)); // 初始化随机数种子
//*L = (LinkList)malloc(sizeof(Node)); // L为整个线性表
*L = new Node; //创建一个头节点,并将其地址赋值给 L 指针,即将链表的头指针指向头节点。
r = *L; //将 r 指针指向头节点,用于记录链表的尾部。
for(i = 0;i < n;i++)
{
//p = (Node *)malloc(sizeof(Node)); // 生成新结点
p = new Node; //创建一个新节点 p
p->data = rand()%100+1; //为新节点的数据域赋值,生成一个介于 1 到 100 之间的随机数。
r->next = p; //将新节点 p 连接到链表的尾部
r = p; // 更新 r 的值为 p 的意思是将指针 r 移动到指针 p 所指向的位置
}
r->next = NULL; // 表示当前链表结束
}
int main()
{
LinkList L;
ElemType e;
Status i;
int j, k;
i = InitList(&L);
cout << "初始化L后:ListLength(L)=" << ListLength(L) << endl;
for (j = 1; j <= 5; j++)
i = ListInsert(&L, 1, j);
cout << "在L的表头依次插入1~5后:L.data=";
ListTraverse(L);
cout << "ListLength(L)=" << ListLength(L) << endl;
i = ListEmpty(L);
cout << "L是否空:i=" << i << "(1:是 0:否)" << endl;
i = ClearList(&L);
cout << "清空L后:ListLength(L)=" << ListLength(L) << endl;
i = ListEmpty(L);
cout << "L是否空:i=" << i << "(1:是 0:否)" << endl;
for (j = 1; j <= 10; j++)
ListInsert(&L, j, j);
cout << "在L的表尾依次插入1~10后:L.data=";
ListTraverse(L);
cout << "ListLength(L)=" << ListLength(L) << endl;
ListInsert(&L, 1, 0);
cout << "在L的表头插入0后:L.data=";
ListTraverse(L);
cout << "ListLength(L)=" << ListLength(L) << endl;
GetElem(L, 5, &e);
cout << "第5个元素的值为:" << e << endl;
for (j = 3; j <= 4; j++)
{
k = LocateElem(L, j);
if (k)
cout << "第" << k << "个元素的值为" << j << endl;
else
cout << "没有值为" << j << "的元素" << endl;
}
k = ListLength(L); /* k为表长 */
for (j = k + 1; j >= k; j--)
{
i = ListDelete(&L, j, &e); /* 删除第j个数据 */
if (i == 0)
cout << "删除第" << j << "个数据失败" << endl;
else
cout << "删除第" << j << "个的元素值为:" << e << endl;
}
cout << "依次输出L的元素:";
ListTraverse(L);
j = 5;
ListDelete(&L, j, &e); /* 删除第5个数据 */
cout << "删除第" << j << "个的元素值为:" << e << endl;
cout << "依次输出L的元素:";
ListTraverse(L);
i = ClearList(&L);
cout << "\n清空L后:ListLength(L)=" << ListLength(L) << endl;
CreateListHead(&L, 20);
cout << "整体创建L的元素(头插法):";
ListTraverse(L);
i = ClearList(&L);
cout << "\n删除L后:ListLength(L)=" << ListLength(L) << endl;
CreateListTail(&L, 20);
cout << "整体创建L的元素(尾插法):";
ListTraverse(L);
return 0;
}
单链表的整表删除
bool ClearList(LinkList *L)
{
LinkList p,q;
p = (*L)->next;
while(p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return true;
}
静态链表前提代码
#include <cstring>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#define MAXSIZE 1000 /* 存储空间初始分配量 */
bool visit(char c)
{
cout << c;
return true;
}
/* 线性表的静态链表存储结构 */
struct Component
{
char data;
int cur; //cur:表示指向下一个元素的索引。
};
typedef struct Component StaticLinkList[MAXSIZE];
//静态链表类型名为StaticLinkList,它是一个数组类型,数组的大小由MAXSIZE定义。
//每个元素都是Component类型的结构体,用于表示链表中的节点。
将一维数组space中各分量链成一个备用链表,space[0].cur为头指针,"0"表示空指针
bool InitList(StaticLinkList space)
{
int i;
for (i = 0; i < MAXSIZE-1; i++)
space[i].cur = i+1;
space[MAXSIZE-1].cur = 0; /* 目前静态链表为空,最后一个元素的cur为0 */
return true;
}
若备用空间链表非空,则返回分配的结点下标,否则返回0
int Malloc_SSL(StaticLinkList space)
{
int i = space[0].cur; /* 当前数组第一个元素的cur存的值 */
/* 就是要返回的第一个备用空闲的下标 */
if (space[0]. cur)
space[0]. cur = space[i].cur; /* 由于要拿出一个分量来使用了, */
/* 所以我们就得把它的下一个 */
/* 分量用来做备用 */
return i;
}
将下标为k的空闲结点回收到备用链表
void Free_SSL(StaticLinkList space, int k)
{
space[k].cur = space[0].cur; /* 把第一个元素的cur值赋给要删除的分量cur */
space[0].cur = k; /* 把要删除的分量下标赋值给第一个元素的cur */
}
返回L中数据元素个数
int ListLength(StaticLinkList L)
{
int j = 0;
int i=L[MAXSIZE-1].cur;
while(i)
{
i = L[i].cur;
j++;
}
return j;
}
在L中第i个元素之前插入新的数据元素e
bool ListInsert(StaticLinkList L, int i, char e)
{
int j, k, l;
k = MAXSIZE - 1; /* 注意k首先是最后一个元素的下标 */
if (i < 1 || i > ListLength(L) + 1)
return false;
j = Malloc_SSL(L); /* 获得空闲分量的下标 */
if (j)
{
L[j].data = e; /* 将数据赋值给此分量的data */
for(l = 1; l <= i - 1; l++) /* 找到第i个元素之前的位置 */
k = L[k].cur;
L[j].cur = L[k].cur; /* 把第i个元素之前的cur赋值给新元素的cur */
L[k].cur = j; /* 把新元素的下标赋值给第i个元素之前元素的ur */
return true;
}
return false;
}
删除在L中第i个数据元素
bool ListDelete(StaticLinkList L, int i)
{
int j, k;
if (i < 1 || i > ListLength(L))
return false;
k = MAXSIZE - 1;
for (j = 1; j <= i - 1; j++)
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SSL(L, j);
return true;
}
遍历列表
bool ListTraverse(StaticLinkList L)
{
int j = 0;
int i = L[MAXSIZE-1].cur;
while(i)
{
visit(L[i].data);
i = L[i].cur;
j++;
}
return j;
cout<<"\n";
return true;
}
int main()
{
StaticLinkList L;
Status i;
i = InitList(L);
cout<<"初始化L后:L.length=%d\n",ListLength(L);
i = ListInsert(L,1,'F');
i = ListInsert(L,1,'E');
i = ListInsert(L,1,'D');
i = ListInsert(L,1,'B');
i = ListInsert(L,1,'A');
cout << "\n在L的表头依次插入FEDBA后:\nL.data=";
ListTraverse(L);
i = ListInsert(L,3,'C');
cout << ("\n在L的“B”与“D”之间插入“C”后:\nL.data=";
ListTraverse(L);
i = ListDelete(L,1);
cout<<"\n在L的删除“A”后:\nL.data=";
ListTraverse(L);
cout<<"\n";
return 0;
}
双向链表
双向链表的初始化
双链表的插入
双链表的删除
双链表的遍历
循环链表
循环双链表
误区
int main()
{
Linklist L;
Initlist(&L);
Insertlist(&L, 1, 1);
Insertlist(&L, 2, 2);
Insertlist(&L, 3, 3);
Insertlist(&L, 4, 4);
cout << L;
}//输出链表的地址并不会打印链表的内容。如果要打印链表的内容,
//可以编写一个遍历链表的函数,并使用该函数输出链表的元素值。