目录
单链表:
通过单向链表可以很方便的获取每一个接点的直接后继,但要获取直接前驱比较麻烦。其中包含数据域和链域。通常,链表的第一个结点不含任何数据信息,称为头结点,其链域为空时,链表为空链表。
#include<iostream>
using namespace std;
typedef int datatype;
//1.链表的表示
//链表结点的类型定义:设置clNode和*chainList两种形式,是为了后面避免二级指针的出现
//二级指针的使用,一定要注意一级指针指向地址,切勿为空
typedef struct clNode {
datatype data; //数据域
clNode* next; //链域
//一定要对每个结点进行初始化操作,不然创建的结点链域无指向,为野指针
//不然就在初始化的时候,new clNode{x,null} 进行初始化
clNode() :next(NULL) {
data = 0;
}
}*chainList;
//2.链表的基本操作
//2.1遍历链表:从头结点出发,通过每个结点的链域可以访问下一个节点,直到最后一个结点为止
void cl_traverse(chainList h)
{
if (h == NULL) return;
h = h->next; //指向链表的第一个结点(与头结点区分)
while (h != NULL)
{
cout << h->data << endl;
h = h->next;
}
cout << endl;
}
//2.2查找操作:查询值为x的结点,需要进行遍历,同时对结点的data进行判断
chainList cl_search(chainList h, datatype x) //此处的指针需要变换,不能加const
{
if (h == NULL) return NULL;
h = h->next;
while (h != NULL) {
if (h->data == x)
return h;
h = h->next;
}
return NULL;
}
//2.3插入节点:在链表的结点p后面插入一个数据域的值为x的结点q
//当链表中的一个结点p后面插入一个结点时,只需要重新定义p的链域和插入结点q的链域
//第二个思路,通过函数cl_search获取某个值的位置p,然后通过cl_insert函数在p的后面插入指定值的结点
void cl_insert(chainList p, datatype x)
{
chainList q = new clNode;//定义新的结点,用指针指向新定义的指针
//判断是否有足够的空闲存储空间存放新结点
if (q == NULL)
{
cout << "插入结点失败!" << endl;
return;
}
q->data = x;
q->next = p->next;
p->next = q;
}
//若实现pushback尾插,则包含cl_traverse函数得到最后一个结点p,用cl_insert函数在p后插入结点
//2.4删除p结点的直接后继操作
//删除头结点后的结点
void cl_delete(chainList p)
{
if (p == NULL)return;
chainList q = p->next;
if (q == NULL)return;
p->next = q->next;
delete q; //释放了q所占用的空间,但q指针仍有指向
q = NULL; //设置为空指针
}
//删除指定位置后面的结点
void cl_delete(chainList h, datatype x)
{
chainList p = new clNode;
p = cl_search(h, x);
if (p == NULL)return;
chainList q = p->next;
if (q == NULL)return;
p->next = q->next;
delete q; //释放了q所占用的空间,但q指针仍有指向
q = NULL; //设置为空指针
}
//2.5创建链表
void cl_create(chainList& h, datatype a[], int n)
{
if (h == NULL) h = new clNode;//若表头为空,为表头动态分配存储空间
for (int i = n - 1; i >= 0; i--) //将数组a的元素从后向前插入链表中,尾插
cl_insert(h, a[i]);
}
//2.6删除链表
void cl_destroy(chainList& h)
{
//int cnt = 0;
//每行都会执行过去
while (h->next != NULL)
{
cl_delete(h); //删除后,第一个结点的值不断进行改变,所以不需要用h=h->next
//cout << cnt++ << endl;
}
//确保删除完毕
delete h;
h = NULL;
}
int main()
{
int a[] = { 1,4,7,2,5,8,3,6,9 };
chainList header = NULL;
//创建链表
cl_create(header, a, sizeof(a) / sizeof(int));
//遍历链表
cl_traverse(header);
//查找值为5的结点
chainList search = new clNode;
search = cl_search(header, 5);
cl_traverse(search);
//删除5后面的直接后继
cl_delete(header, 5);
cl_traverse(header);
//头结点后插入8
cl_insert(header, 8);
cl_traverse(header);
//删除整个列表
cl_destroy(header);
cl_traverse(header);
}
在单向链表中,可以高效的确定每一个结点的直接后继,但确定结点的直接前驱比较麻烦,从而查找效率降低。为了增加链表操作的灵活性,可以对单向链表做一些改变,得到另外两种类型的链表:双向链表和循环链表。
双向链表:
含一个数据域和两个链域(前驱链域和后继链域)。同样,和单链表一样都用第一个结点header表示整个链表,但所不同的是,双向链表的header结点的数据域存放有效数据,不为空。
优点:很方便的实现双向查找,插入结点可前可后
劣势:由于增加了一个前驱链域,双向链表比单向链表多了一些存储空间的开销。此外,由于在操作中需要处理前驱链域,双向链表操作的效率比单向链表低。(尽量使用单链表)
#include<iostream>
using namespace std;
typedef int datatype;
//双向链表的结点的类型定义
typedef struct dclNode {
datatype data;
dclNode* pre;
dclNode* next;
dclNode() :pre(NULL), next(NULL) {
data = 0;
}
}*dchainList;
//在结点p的后面插入一个值为x的结点
void dcl_insert_post(dchainList& p, datatype x)
{
dchainList q = new dclNode, nxt = p->next;//nxt的出现只为了简介的表达
q->data = x;
//四个连接
if (nxt != NULL) nxt->pre = q;
q->next = nxt;
p->next = q;
q->pre = p;
}
//在结点p的前面插入一个值为x的结点
void dcl_insert_pre(dchainList& p, datatype x) //改变链表需要取址符
{
dchainList q = new dclNode, pre = p->pre;
q->data = x;
p->pre = q;
q->next = p;
if (pre != NULL) {
pre->next = q;
q->pre = pre;
}
else //在表头的前面插入,需更新表头,p与q都代表的是指针,而不是确切的存储空间
p = q;
}
//创建双向链表,包含n个结点,节点数据存放在数组a中
void dcl_create(dchainList& h, datatype a[], int n)
{
h = new dclNode; //首先需要构建第一个结点,并且第一个结点应有数据域
h->data = a[0];
for (int i = n - 1; i > 0; i--) {
dcl_insert_post(h, a[i]);
}
}
//删除双向链表h中第一个值为x的结点
void dcl_delete(dchainList& h, datatype x)
{
dchainList dcl = h;
//寻找位置的指向
while (dcl != NULL) {
if (dcl->data == x) break; //dcl的指向已经到达了所要的position
dcl = dcl->next;
}
if (dcl == NULL) return;
//pre和nxt分别为删除节点的直接前驱和直接后继
dchainList pre = dcl->pre, nxt = dcl->next;
//进行变动,只需改动前后的直接后继和直接前驱,
//有头结点,尾结点和一般结点三种情况
//一般情况
if (pre != NULL) pre->next = nxt; //尾结点仅此一步操作即可
if (nxt != NULL) nxt->pre = pre;
//头结点情况+上面的nxt!=NULL,赋予直接前驱NULL的初始化,防止野指针
if (dcl == h) h = nxt;
delete dcl;dcl = NULL;
}
//同单链表一样查找
void dcl_nxt_print(dchainList h)
{
while (h != NULL) {
cout << h->data << endl;;
h = h->next;
}
cout << endl;
}
//前驱节点查找
void dcl_pre_print(dchainList h)
{
/*
这样的做法有误,因为当h = NULL时,没有前驱结点,无法调回最后一个结点
while (h != NULL) {
cout << h->data << endl;;
h = h->next;
}
//调回至最后一个结点
h = h->pre;
*/
while (h->next != NULL)
h = h->next;
while (h != NULL)
{
cout << h->data << endl;
h = h->pre;
}
cout << endl;
}
int main()
{
int a[] = { 1,4,7,2,5,8,3,6,9 };
dchainList h;
dcl_create(h, a, sizeof(a) / sizeof(int));
dcl_nxt_print(h);
dcl_pre_print(h);
}
循环链表:
在不带头结点的单向链表中,最后一个结点的链域为空指针,如果将该链域指向第一个结点,则该类链表成为循环链表。
与单向链表相比,循环链表的优势是可以从任意结点出发访问链表的所有结点;与双向链表相比,循环链表不需要额外的存储开销。
为了与单向链表的操作一致,通常用最后一个结点来表示循环链表。
#include<iostream>
using namespace std;
typedef int datatype;
//1.链表的表示
//链表结点的类型定义:设置clNode和*chainList两种形式,是为了后面避免二级指针的出现
//二级指针的使用,一定要注意一级指针指向地址,切勿为空
typedef struct rclNode {
datatype data; //数据域
rclNode* next; //链域
//一定要对每个结点进行初始化操作,不然创建的结点链域无指向,为野指针
//不然就在初始化的时候,new clNode{x,null} 进行初始化
rclNode():next(NULL) {
data = 0;
}
}*chainList;
//查询循环链表中是否存在值为x的结点
chainList rcl_search(chainList h, datatype x)
{
if (h == NULL) return NULL;
chainList h1 = h;
do {
h1 = h1->next;
if (h1->data == x)return h1;
} while (h1 != h);
return NULL;
}
//在结点p的后面插入一个值为x的结点
void rcl_insert(chainList& p, datatype x)
{
chainList q = new rclNode;
if (q == NULL)
{
cout << "插入节点失败!" << endl;
return;
}
q->data = x;
if (p == NULL)
q->next = q, p = q;
else //同单链表
q->next = p->next, p->next = q;
}
//创建循环链表h,共有n个结点,每一个节点的数据域存放在数组a中
void rcl_creater(chainList& h, datatype a[], int n)
{
rcl_insert(h, a[n - 1]); //先构建最后一个结点
for (int i = n - 2; i >= 0; i--) //将数组a的元素从后往前一次插入链表中
rcl_insert(h, a[i]);
}
//删除循环链表中p的直接后继
void rcl_delete(chainList& p)
{
if (p == NULL)return;
chainList q = p->next;
if (p == q) {
delete p;
p = NULL;
return;
}
p->next = q->next; //重新定义p的链域
delete q; //释放q所占用的存储空间
q = NULL;
}
//删除循环链表h中的所有结点
void rcl_destroy(chainList& h)
{
while (h != NULL)
rcl_delete(h);
}
void rcl_print(chainList h)
{
if (h == NULL) return;
chainList h1 = h;
do {
h1 = h1->next;
cout << h1->data << endl;
} while (h1 != h);
cout << endl;
}
int main()
{
int a[] = { 1,4,7,2,5,8,3,6,9 };
chainList header = NULL;
//创建链表
rcl_creater(header, a, sizeof(a) / sizeof(int));
rcl_print(header);
//从5后面的结点的位置开始遍历
chainList result = new rclNode;
result = rcl_search(header, 5);
rcl_print(result);
//添加位置在第一个元素
rcl_insert(header, 666);
rcl_print(header);
//删除第一个元素
rcl_delete(header);
rcl_print(header);
//删除整个链表
rcl_destroy(header);
rcl_print(header);
}
以上为基于单向链表所构建的循环链表,也可以基于双向链表构建循环链表,称为双向循环链表。其中一般用第一个结点表示双向循环链表。
一些有待商榷的问题:
参考:
1.有关结构体typedef struct Node{ }Node, *LinkedList;的解惑_coder_by的博客-CSDN博客