链表——更好的利用空间

148 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情


一、链表的概念

1.链表
顺序表需要一段连续的存储单元来存放元素,这种结构形式不能充分利用存储空间,为了克服顺序表的这一缺点,可以考虑采用非连续的方式存储线性表的元素,而元素之间的先后关系通过指针表示,这种线性表的链式存储结构称为链表。

二、单向链表的表示与操作

1.单向链表
单向链表是指链表中的每一个结点(元素)都有一个指向下一个结点(直接后继)的指针,这样从第一个结点出发就可以遍历所有结点。显然,通过单向链表可以很方便获取每一个结点的直接后继,但要获取直接前驱则比较麻烦。
2.数据域
结点包含数据信息的域
3.链域
指向其直接后继结点的指针(称为链域)。由于链表的最后一个结点(表尾)没有直接后继,则规定其链域为空指针
4.头结点
只要获取链表的第一个结点,就能通过链域访问链表的每一个结点,因此通常用链表的第一个结点表示链表;另一种表示链表的方法是在链表的第一个结点前添加一个不包含任何数据信息的结点,称为头结点,即头结点的链域指向链表的第一个结点,当头结点的链域为空指针时,则表示的链表为空链表

1.链表的表示

1.链表结点类型定义
每个结点包含2个属性:存放信息元素的数据域和存放后继结点指针的链域
构造函数将结点的链域初始化为空指针

//链表结点的类型定义
typedef struct clNode {
	int data;//数据域
	clNode* next;//链域
	clNode():next(NULL){}
}*chainList;

2.链表的基本操作

1)遍历链表

方法:从链表的头结点h出发,通过每个结点的链域可以访问下一个结点,直到最后一个结点

//遍历链表
void cl_traverse(chainList h) {
	if (h == NULL)return;
	h = h->next;//指向头结点的下一个结点
	while (h != NULL) {
		cout << h->data << ' ';
		h = h->next;//h指向下一个结点
	}
	cout << endl;
}

2)查找操作

方法:查询链表中是否存在值为x的结点,对链表进行遍历,判断值是否为x

//查询链表h中是否存在值为x的结点,如果存在,返回值为x的第一个结点的物理位置,否则返回空指针
chainList cl_search(chainList h, int x) {
	if (h == NULL)return NULL;
	h = h->next;
	while (h != NULL) {
		if (h->data == x)return h;//找到结点
		h = h->next;
	}
	return NULL;
}

3)插入操作

算法1-1:插入操作
功能
在链表中结点p的后面插入数据域的值为x的结点q
步骤
(1)为q动态分配存储空间,并指定其数据域的值为x
(2)将q的链域指向p的链域
(3)将p的链域指向q

//在结点p的后面插入一个值为x的结点
void cl_insert(chainList p, int x) {
	chainList q = new clNode;//创建新结点
	if (q == NULL) {
		cout << "插入结点失败";
		return;
	}
	q->data = x;//给新结点的元素赋值
	q->next = p->next;
	p->next = q;//重新定义p的链域
}
//创建链表h,共有n个结点,每一个结点的数据域存放在数组a中
void cl_creater(chainList& h, int a[], int n) {
	if (h == NULL)h = new clNode;//如果表头为空指针,则为表头动态分配存储空间
	for (int i = n - 1; i >= 0; i--)
		cl_insert(h, a[i]);//将数组a的元素从后向前依次插入链表中
}

4)删除操作

算法1-2:删除链表中的结点
功能
删除链表header中结点p的直接后继
步骤
(1)保存p的直接后继q
(2)将p的链域重新定义为q的链域
(3)释放q所占用的存储空间

//删除链表中结点p的直接后继
void cl_delete(chainList p) {
	if (p == NULL)return;
	chainList q = p->next;
	if (q == NULL)return;//p为表尾
	p->next = q->next;//重新定义p的链域
	delete q;//释放q所占用的空间
	q = NULL;
}
//删除链表h中的所有结点和表头
void cl_destroy(chainList& h) {
	while (h->next != NULL)
		cl_delete(h);
	delete h;
	h = NULL;
}

三、双向链表与循环链表

1.双向链表

1.双向链表定义

1.定义
在单向链表的每个结点中添加一个指向其直接前驱的链域,即可以向后搜索,又可以向前搜索\

2.类型定义

typedef struct dclNode {
	int data;
	dclNode* pre;
	dclNode* next;
	dclNode():pre(NULL),next(NULL){}
}*dchainList;

2.基本操作

1.插入

void dcl_insert_post(dchainList& p, int x) {
	dchainList q = new dclNode, nxt = p->next;
	q->data = x;
	if (nxt != NULL)
		nxt->pre = q;//nxt的直接前驱为q
	q->next = nxt;//q的直接后继为nxt
	p->next = q;
	q->pre = p;
}

2.删除

void dcl_delete(dchainList& h, int x) {
	dchainList dcl = h;
	while (dcl != NULL)//遍历链表,寻找第一个值为x的节点
	{
		if (dcl->data == x)break;
		dcl = dcl->next;
	}
	if (dcl == NULL)return;//不存在值为x的节点
	dchainList pre = dcl->pre, nxt = dcl->next;
	if (pre != NULL)pre->next = nxt;
	if (nxt != NULL)nxt->pre = pre;
	if (dcl == h)h = nxt;//如果删除的是第一个结点,则用其直接后继表示新的双向链表
	delete dcl;
	dcl = NULL;
}

2.循环链表

1.循环链表定义

1.代码

typedef struct clNode {
	int data;
	clNode* next;
	clNode():next(NULL){}
}*chainList;

注意循环链表的header表示的是最后一个结点

2.基本操作

1.查询操作

chainList rcl_search(chainList h, int x) {
	if (h == NULL)return NULL;
	chainList h1 = h;
	do {
		h1 = h1->next;
		if (h1->data == x)return h1;//找到节点
	} while (h1 != h);
	return NULL;///没有找到
}

2.插入操作

void rcl_insert(chainList& p, int x) {
	chainList q = new clNode;
	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;//重新定义p,q的链域
}
//创建循环链表,共有n个结点,每一个结点的数据域存放在数组a中
void rcl_creater(chainList& h, int a[], int n) {
	rcl_insert(h, a[n - 1]);//先构建最后一个结点
	for (int i = n - 2; i >= 0; i--)//将数组从后往前依次插入
		rcl_insert(h, a[i]);
}

3.删除

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;
	delete q;
	q = NULL;
}