数据结构与算法:链表


1.链表定义

链表是一种线性是数据结构,其物理储存空间不一定连续,链表由若干的节点组成,每个节点包含数据域和指针域,数据域存放自定义变量,指针域存放指向下一个节点的指针。
在这里插入图片描述

我们通过头指针进入并访问链表,头指针指向第一个节点,即首元节点,为了使每个节点在处理时方式一样,我们也可以人为设置一个头节点(不是必须的)。
在这里插入图片描述

头指针与头节点异同如下:
在这里插入图片描述

2.单向链表

单向链表是指每个节点的指针域只含一个指针的链表。常用操作包括插入节点,删除节点,头部插入初始化,尾部插入初始化,销毁链表等。下面逐步实现一个带头节点的单向链表。

2.1读取

获得链表的第index个数据。首先要分析输入的范围,index≥1index不大于表的长度。
具体算法为:
先检验index是否≤0,若成立则输入不合法。
反之:
1.引入Node*型变量p获取头指针的指向,引入计数变量i =1;
2.当计数变量i<index且p的指针域非空时,计数变量自加1,并将p的指针域赋值给p;不断重复此操作(输出第index个位置的节点的指针p)
3.判断p是否为空,若为空则说明索引大于表的长度,若非空则输出对应位置的元素
实现代码如下:

void List::GetElem(int index)
{
	if (index <= 0)
	{
		cout << "索引非法!" << endl;
	}
	else
	{
		Node* p = head->next;//获取指向首元节点的指针
		int i = 1;
		while (i < index && p)//未达到索引处且指向当前的指针不为空
		{
			p = p->next;
			i++;
		}//输出第index个位置的节点的指针
		if (!p)
		{
			cout << "节点不存在!" << endl;
		}
		else
		{
			cout << "第" << index << "个位置的节点为:" << p->data << endl;
		}
	}
}

2.2节点在指定位置插入

在链表的第index个数据前插入新元素。首先要分析输入的范围,index≥1index不大于表的长度。
在这里插入图片描述
只需要将节点s的指向变为节点p的指向,并令p指向s即可。这两步的顺序不可颠倒,若先令p指向s则无法再访问p->next。操作示意图如下:
在这里插入图片描述
算法流程如下:
先检验index是否≤0,若成立则输入不合法。
反之:
1.引入Node*型变量p获取头指针的指向,引入计数变量i =1;
2.当计数变量i<index-1且p的指针域非空时,计数变量自加1,并将p的指针域赋值给p;不断重复此操作(输出第index-1个位置的节点的指针p);
3.判断p是否为空,若为空则说明索引大于表的长度,若非空则执行插入操作。

插入操作即先在堆区开辟一个Node*型变量s,其指针域用于储存第index-1个位置的节点的指针p,即原来第index个位置的节点的指针,并让p指向s,同时完成s数据域的赋值。
实现代码如下:

void List::Insert(const int index, DataType e)
{
	if (index <= 0)
	{
		cout << "索引非法!" << endl;
	}
	else
	{
		Node* s = new Node;//创建一个新节点,p为指向新节点的指针
		Node* p = head;//获取头节点
		s->data = e;
		int i = 1;
		while (i < index && p)
		{
			p = p->next;
			i++;
		}//输出第index-1个位置的节点的指针
		if (!p)
		{
			cout << "索引过大!" << endl;
		}
		else
		{
			s->next = p->next;
			p->next = s;
			cout << "第" << index << "个位置的节点插入完毕!" << endl;
		}
	}
}

2.3删除指定位置的节点

在这里插入图片描述
下面的q表示一个变量。
要将节点a(i-1)绕过节点a(i)指向下一个节点。可以先用一个Node*型变量q储存节点a(i-1)的指针域,即p->next,再用p指向q的指针域,此时p的指针域变为p->next->next,然后释放q。引入变量q是必要的,不然无法释放q对应的内存。
算法思路与插入类似,具体的实现代码如下

void List::Delete(int index)
{
	if (index<=0)
	{
		cout << "索引非法!" << endl;
	}
	else
	{
		Node* p = head;//获取头节点
		int i = 1;
		while (i < index && p)
		{
			p = p->next;
			i++;
		}//输出第index-1个位置的节点的指针
		Node* q = p->next;//获取指向第index个位置的指针
		p->next = q->next;//将第index+1个位置的指针赋给第index-1个位置的节点的指针域
		delete q;//删除第index个位置的节点
		cout << "第" << index << "个位置的节点删除完毕!" << endl;
	}
}

2.4链表的整表创建

包括头插法和尾插法。
头插法算法流程:
1.在堆区创建一个新节点;
2.新节点指向头指针的指向;
3.头指针指向新创建的节点。
具体实现代码如下:

void List::CreatListHead(int n)
{
	for (int i = 0; i < n; i++)
	{
		Node* p = new Node;
		p->data = rand() % 100;
		p->next = head->next;
		head->next = p;
		cout << "第" << i + 1 << "次操作在首元节点前插入了" << p->data << endl;
	}
	system("pause");
	system("cls");
}

尾插法算法流程:
1.在堆区创建一个新节点;
2.新节点指向尾指针的指向,即NULL;
3.尾指针指向新创建的节点。
具体实现代码如下:

void List::CreatListTail(int n)
{
	for (int i = 0; i < n; i++)
	{
		Node* p = new Node;
		p->data = rand() % 100;
		
		Node* q = head;
		while (q->next)
		{
			q = q->next;
		}//找到指向尾节点的指针q
		q->next = p;
		p->next = NULL;//p为新的尾节点
		cout << "第" << i + 1 << "次操作在尾节点后插入了" << p->data << endl;
	}
	system("pause");
	system("cls");

}

2.5链表的销毁

算法流程如下:
1.声明一个Node*型变量q;
2.q获取头指针的指向,即第1个节点的地址;
3.头指针指向下一个节点;
4.释放q指向的内存;
重复至头指针指向的节点为空。
具体实现代码如下:

void List::ClearList()
{
	Node* q;
	while (head->next)//跳出循环的条件为头指针指向空
	{
		q = head->next;//获取头指针指向的节点
		head->next = q->next;//头指针指向下一个节点
		delete q;//销毁之前的首元节点
	}
	cout << "销毁完毕!" << endl;

}

3.完整的C++实现

3.1 LinkList.h文件的编写

#ifndef __LINKLIST__
#define __LINKLIST__
//不带头节点的单向链表
#include<iostream>
#include <cstdlib>
using namespace std;

typedef int DataType;

class Node
{
public:
	DataType data;
	Node* next;
};


class List
{
public:
	List();
	void GetElem(int index);//读取第index个位置的节点的元素
	void Insert(const int index, DataType e);//在第index个位置前插入节点
	void Delete(int index);//删除第index个节点
	void ShowList();//读取整个链表
	void CreatListHead(int n);//在头节点与首元节点间依次插入n个节点
	void CreatListTail(int n);//在尾节点后依次插入n个节点
	void ClearList();//销毁表
	int ListLength();//计算表长
	void IsEmpty();
private:
	Node* head;//头节点
};

#endif

3.2 LinkList.cpp文件的编写

#include"LinkList.h"

List::List()
{
	head = new Node;
	head->next = NULL;
}

void List::GetElem(int index)
{
	if (index <= 0)
	{
		cout << "索引非法!" << endl;
	}
	else
	{
		Node* p = head->next;//获取指向首元节点的指针
		int i = 1;
		while (i < index && p)//未达到索引处且指向当前的指针不为空
		{
			p = p->next;
			i++;
		}//输出第index个位置的节点的指针
		if (!p)
		{
			cout << "节点不存在!" << endl;
		}
		else
		{
			cout << "第" << index << "个位置的节点为:" << p->data << endl;
		}
	}
}

void List::Insert(const int index, DataType e)
{
	if (index <= 0)
	{
		cout << "索引非法!" << endl;
	}
	else
	{
		Node* s = new Node;//创建一个新节点,p为指向新节点的指针
		Node* p = head;//获取头节点
		s->data = e;
		int i = 1;
		while (i < index && p)
		{
			p = p->next;
			i++;
		}//输出第index-1个位置的节点的指针
		if (!p)
		{
			cout << "索引过大!" << endl;
		}
		else
		{
			s->next = p->next;
			p->next = s;
			cout << "第" << index << "个位置的节点插入完毕!" << endl;
		}
	}
}

void List::Delete(int index)
{
	if (index<=0)
	{
		cout << "索引非法!" << endl;
	}
	else
	{
		Node* p = head;//获取头节点
		int i = 1;
		while (i < index && p)
		{
			p = p->next;
			i++;
		}//输出第index-1个位置的节点的指针
		Node* q = p->next;//获取指向第index个位置的指针
		p->next = q->next;//将第index+1个位置的指针赋给第index-1个位置的节点的指针域
		delete q;//删除第index个位置的节点
		cout << "第" << index << "个位置的节点删除完毕!" << endl;
	}
}

void List::ShowList()
{
	Node* p = head->next;//获取指向首元节点的指针
	int i = 1;
	while (p)
	{
		cout << "第" << i << "个节点为:" <<p->data<< endl;
		i++;
		p = p->next;
	}
}

void List::CreatListHead(int n)
{
	for (int i = 0; i < n; i++)
	{
		Node* p = new Node;
		p->data = rand() % 100;
		p->next = head->next;
		head->next = p;
		cout << "第" << i + 1 << "次操作在首元节点前插入了" << p->data << endl;
	}
	system("pause");
	system("cls");
}

void List::CreatListTail(int n)
{
	for (int i = 0; i < n; i++)
	{
		Node* p = new Node;
		p->data = rand() % 100;
		
		Node* q = head;
		while (q->next)
		{
			q = q->next;
		}//找到指向尾节点的指针q
		q->next = p;
		p->next = NULL;//p为新的尾节点
		cout << "第" << i + 1 << "次操作在尾节点后插入了" << p->data << endl;
	}
	system("pause");
	system("cls");

}

void List::ClearList()
{
	Node* q;
	while (head->next)//跳出循环的条件为头指针指向空
	{
		q = head->next;//获取头指针指向的节点
		head->next = q->next;//头指针指向下一个节点
		delete q;//销毁之前的首元节点
	}
	cout << "销毁完毕!" << endl;

}

int List::ListLength()
{
	int i = 0;
	Node* p = head->next;
	while (p)
	{
		i++;
		p = p->next;
	}
	return i;
}

void List::IsEmpty()
{
	if (head->next)
	{
		cout << "链表非空" << endl;
	}
	else
	{
		cout << "空链表" << endl;
	}
}

3.3 main.cpp文件的编写

#include<iostream>
using namespace std;
#include"LinkList.h"

void showLine()
{
	cout << "--------------------------------" << endl;
}
int main()
{
	List L1;

	cout << "头插实验:" << endl;
	L1.CreatListHead(5);
	L1.ShowList();

	cout << "尾插实验:" << endl;
	L1.CreatListTail(5);
	L1.ShowList();

	cout << "元素读取实验:" << endl;
	L1.GetElem(8);
	L1.GetElem(10);
	L1.GetElem(11);
	showLine();

	cout << "节点删除实验:" << endl;
	L1.Delete(2);
	L1.Delete(0);
	L1.ShowList();
	showLine();


	cout << "销毁实验:" << endl;
	L1.ClearList();
	cout<<"表长为:"<<L1.ListLength()<<endl;
	L1.ShowList();
	L1.IsEmpty();
	return 0;
}

4.循环链表

循环链表与单链表的区别是,其尾节点的指针域存着指向头节点的指针,从而形成一个闭环结构。
在这里插入图片描述
当链表为空时,判断条件不再是头节点的指向为空,而是头节点的指针指向自己。
在这里插入图片描述

4.1尾指针

在单向链表中,访问首元节点的时间复杂度为o(1),而访问尾节点的时间复杂度为o(n),为了降低访问尾节点的时间复杂度,引入尾指针。
在这里插入图片描述
记作Node*型变量rear存放一个访问尾节点的指针,那么rear->next即为头节点,rear->next->next即为首元节点,访问的时间复杂度均为o(1)

4.2循环链表合并

此外,通过引入尾指针,将两个循环链表进行合并时,令A链表的尾节点指向B链表的头节点,令B链表的尾节点指向A链表的头节点,保留链表A的头节点作为合并后的链表的头节点,只需进行如下操作:

在这里插入图片描述
1.引入Node*型变量p保存rearA->next,即链表A头节点的地址;
2.A链表的尾节点指向B链表的首元节点;
3.释放链表B头节点;
4.B链表的尾节点指向A链表的头节点,即p;
用代码表示如下:

Node* p = rearA->next;
rearA->next = rearB->next->next;
delete rearB->next;
rearB->next = p;

在这里插入图片描述

5.双向链表

5.1普通双向链表

双向链表的指针域中,不仅存放着后继节点的地址,也存放着前驱节点的地址。
在这里插入图片描述
非空的双向链表结构如图所示:

当其为空表时,头节点的prior域与next域都为空。

5.2循环双向链表

与单向循环链表相似,头节点的prior指向尾节点,尾节点的next指向头节点。
在这里插入图片描述
其空表为:头节点的前驱与后继节点均指向自己。
在这里插入图片描述
双向循环链表具有对称性

p->prior->next = p = p->next->prior;

5.3双向链表节点的插入

在这里插入图片描述
先定位节点p,p为Node*型变量,存放指向p节点的地址,在节点p与前一个节点间插入节点s
具体操作如下:
1.s前驱指向p的前驱;
2.s后继指向p
3.p的前驱的后继修改为s;
4.p的前驱改为s
实现代码如下:

s->prior = p->prior;
s->next = p;
p->prior->next = s;
p->prior  = s;

要注意修改p的前驱的后继时必须在修改p的前驱为s之前。

5.4双向链表节点的删除

在这里插入图片描述
先定位节点p,p为Node*型变量,存放指向p节点的地址。将p的前驱的后继改为p的后继,将p的后继的前驱改为p的前驱,两者次序可交换。之后释放p即可。

6.小结

在这里插入图片描述
但是使用链表要开辟指针域,需要更大的内存存放数据,用空间换时间。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值