数据结构(3)--双链表,循环链表,静态链表

一.双链表

1.1 说明

        单链表结点中只有一个指向其后继的指针,使得单链表只能从头结点依次顺序地向后遍历。要访问某个结点的前驱结点(插入,删除操作时),只能从头开始遍历,访问后继结点的时间复杂度为O(1),访问前驱结点的时间复杂度为O(n)。

        但如果使用双链表,双链表结点中有两个指针prev和next,分别指向其前驱结点和后继结点。就能非常轻松地同时找到某个结点的前驱结点和后继节点。插入和删除的复杂度仅为O(1)。

 1.2 函数说明

1.2.1 结点描述

typedef struct DNode {
	Elemtype data;
	struct DNode* prior, * next;
}DNode,*DLinklist;

1.2.2 初始化(带头结点)

bool Initlist(DLinklist& L)
{
	L = new DNode;
	if (!L)
	{
		cout << "申请节点失败" << endl;
		return false;
	}
	else
	{
		L->next = NULL; //初始化后继指针
		L->prior = NULL; //初始化前驱指针
		return true;
	}
}

1.2.3 使用头插法创建双链表

void HeadInsert(DLinklist& L)
{
	int n;  //输入元素的个数
	cout << "头插法--请输入元素个数n:" << endl;
	cin >> n;
	DLinklist s;  //定义指针变量s
	cout << "请依次输入元素:" << endl;
	while (n--)
	{
		s = new DNode;
		cin >> s->data;
		s->next = L->next;
		s->prior = L;
		L->next = s;
	}
}

1.2.4 使用尾插法创建双链表

void TailInsert(DLinklist& L)
{
	int n;  //输入元素的个数
	cout << "头插法--请输入元素个数n:" << endl;
	cin >> n;
	DLinklist s;  //定义指针变量s
	DLinklist r;  //定义一个表尾指针r
	r = L;
	cout << "请依次输入元素:" << endl;
	while (n--)
	{
		s = new DNode;
		cin >> s->data;
		s->next = NULL;
		r->next = s;
		s->prior = r;
		r = s;
	}
}

1.2.5 打印链表

void Printlist(DLinklist& L)
{
	DLinklist s;
	s = L->next;
	while (s)
	{
		cout << s->data << " ";
		s = s->next;
	}
	cout << endl;
}

1.2.6 求链表的长度

int Lengthlist(DLinklist& L)
{
	DLinklist s;
	s = L->next;
	int length= 0;
	while (s)
	{
		length++;
		s = s->next;
	}
	return length;
}

1.2.7 按位置序号查找节点

DNode* GetElem(DLinklist& L, int i)
{
	if (i<1 || i>Lengthlist(L))
		return NULL;
	else
	{
		int j = 1;
		DNode* p = L->next; //把第一个结点赋值给p;
		while (j < i)
		{
			p = p->next;
			j++;
		}
		return p;
	}
}

1.2.8 按值查找位置

int LocateElem(DLinklist& L, Elemtype e)
{
	DNode* p = L->next; //第一个结点赋给p;
	int j = 1;
	while (p != NULL && p->data != e)
	{
		p = p->next;
		j++;
	}
	return j;
}

1.2.9 双链表在第i个位置插入数据

bool ListInsert(DLinklist& L, int i, Elemtype e)
{
	if (i<1 || i>Lengthlist(L)+1)
		return false;
	else
	{
		DLinklist p;
		p = GetElem(L, i); //原本的第i个结点
		DLinklist s; //想要插入的结点s
		s = new DNode;
		s->data = e;
		p->prior->next = s;
		s->prior = p->prior;
		s->next = p;
		p->prior = s;
	}
}

1.2.10 删除双链表第i个位置的链表

bool ListDelete(DLinklist& L, int i)
{
	if (i<1 || i>Lengthlist(L) + 1)
		return false;
	else
	{
		DLinklist p;
		p = GetElem(L, i);//想要删除的结点i
		p->prior->next = p->next;
		p->next->prior = p->prior;
		free(p);
	}
}

1.2.11 main函数

#include<iostream>
using namespace std;
#include"stdlib.h"
#define Elemtype int

int main()
{
	DLinklist L;
	Initlist(L);
	//(1)头插法创建链表
	//HeadInsert(L);
	//Printlist(L);
	//(2)尾插法创建单链表
	TailInsert(L);
	Printlist(L);
	//(3)按位置查找结点
	//DLinklist p;
	//p = GetElem(L, 3);
	//cout << p->data << endl;
	//(4)在第i个位置插入元素
	int n1, n2;
	cout << "插入的位置和元素是" << endl;
	cin >> n1 >> n2;
	ListInsert(L, n1, n2);
	Printlist(L);
	//(5)删除第i个位置的结点
	int n3;
	cout << "删除的位置是" << endl;
	cin >> n3;
	ListDelete(L, n3);
	Printlist(L);
	system("pause");
	return 0;
}

1.3 思考

        与单链表的函数创建上,区别主要在于插入和删除的时候,不仅仅要更新后继指针,还有前驱指针。

二.循环链表

2.1 循环单链表

2.1.1 说明

        循环单链表和单链表的区别在于:表中最后一个结点的指针不是NULL,可改为指向头结点,从而整个链表形成一个环。

        注意:在循环单链表中,表尾结点的next域指向L,故链表中没有指针域为NULL的结点。因此,循环单链表的判空条件不是头结点的指针是否为空,而是它是否等于头指针。

        循环单链表的插入、删除算法与单链表的几乎一样,所不同的是若操作是在表尾进行,则执行的操作不同,来让单链表继续保持循环的性质。当然,正是因为循环单链表是一个"环",因此在任何一个位置上的插入和删除操作都是等价的,无须判断是否是表尾。

        在单链表中只能从表头结点开始,往后顺序遍历整个链表,而循环单链表可以从表中的任意一个结点开始遍历整个链表,有时对循环单链表不设头指针而仅设尾指针,以使得操作效率更高。其原因是:若设的是头指针,对在表尾插入元素需要O(n)的时间复杂度,而若设的是尾指针r->next 即为头指针,对在表头或表尾插入元素都只需要O(1)的时间复杂度。

2.1.2函数说明

#include<iostream>
using namespace std;
#include"stdlib.h"
#define Elemtype int

//定义结点
typedef struct LNode
{
	Elemtype data;
	struct LNode* next; //与单链表定义一致
}LNode,*Linklist;

//(1)初始化(带头结点)
void Initlist(Linklist& L)
{
	L = new LNode;
	L->next = L; //区别单链表的初始化,L的next还是自己
}

//(2)尾插法创建循环单链表
void ListInsert(Linklist& L)
{
	int n; //输入元素的个数
	cout << "尾插法--请输入元素个数n:" << endl;
	cin >> n;
	Linklist r; //定义一个表尾指针
	r = L;
	Linklist s;//定义指针变量s
	cout << "请依次输入元素:" << endl;
	while (n--)
	{
		s = new LNode;
		cin >> s->data;
		s->next = L; //关键区别:插入尾巴的那个元素的next不再是NULL,而是头结点
		r->next = s;
		r = s;
	}
}
//(3)头插法创建循环单链表(与单链表的创建一模一样)
void List_HeadInsert(Linklist& L)
{
	int n;  //输入元素的个数
	cout << "头插法--请输入元素个数n:" << endl;
	cin >> n;
	Linklist s;  //定义指针变量s
	cout << "请依次输入元素:" << endl;
	while (n--)
	{
		s = new LNode; //定义变量结点
		cin >> s->data;
		s->next = L->next;
		L->next = s;
	}
}

//(4)打印链表元素
void Printlist(Linklist& L)
{
	LNode* p = L->next;
	while (p != L) //判断到链尾的条件是:next是头指针
	{
		cout << p->data << " ";
		p = p->next;
	}
	cout << endl << "回到链首,第一个元素是" << p->next->data << endl;
}

int main()
{
	Linklist L;
	Initlist(L);
	//尾插法创建循环单链表
	ListInsert(L);
	Printlist(L);

	system("pause");
	return 0;
}

2.1.3 思考

        循环单链表的操作基本和单链表一致,好处不说了,但从一些函数操作来讲,最为需要注意的点就是在链表的尾部进行操作,无论是删除还是插入,要记得指向头结点即可。

2.2 循环双链表

2.2.1 说明

        循环双链表是对双链表的改进,将尾结点与头结点建立连接,使得尾结点可以直接查找到头结点,头结点也能够直接查找到头结点,以此来提高查找的效率。

2.2.2 函数说明

#include<iostream>
using namespace std;
#include"stdlib.h"
#define Elemtype int

//定义结点
typedef struct LNode
{
	Elemtype data;
	struct LNode* prior, * next;//与双链表定义一致
}LNode,*Linklist;

//(1)初始化(带头结点)
void Initlist(Linklist& L)
{
	L = new LNode;
	L->next = L; 
	L->prior = L; //区别双链表的初始化,L的next和prior还是自己
}

//(2)尾插法创建循环单链表
void ListInsert(Linklist& L)
{
	int n; //输入元素的个数
	cout << "尾插法--请输入元素个数n:" << endl;
	cin >> n;
	Linklist r; //定义一个表尾指针
	r = L;
	Linklist s;//定义指针变量s
	cout << "请依次输入元素:" << endl;
	while (n--)
	{
		s = new LNode;
		cin >> s->data;
		s->next = L; //关键区别1:插入尾巴的那个元素的next不再是NULL,而是头结点
		s->prior = r; //关键区别2:新插入结点的前驱是尾指针
		r->next = s;
		L->prior = s; //关键区别2:L的前驱指针更新为最后的结点
		r = s;
	}
}

//(3)打印链表元素
void Printlist(Linklist& L)
{
	LNode* p = L->next;
	while (p != L) //判断到链尾的条件是:next是头指针
	{
		cout << p->data << " ";
		p = p->next;
	}
	cout << endl << "回到链首,第一个元素是" << p->next->data << endl;
}

int main()
{
	Linklist L;
	Initlist(L);
	//尾插法创建循环单链表
	ListInsert(L);
	Printlist(L);
	system("pause");
	return 0;
}

2.2.3 思考

        难度其实并无太多增加,核心还是在于头结点和最后一个结点之间的关系,在更新中,千万不要不要中间每个结点还要更新前驱指针!(我就会忘)

三.静态链表

        在有些早期的高级语言中,并没有指针概念,所以带有指针域的链表都无法在这些高级语言中使用。于是,出现了用一维数组代替指针来描述单链表,这种一维数组描述的链表就被称为静态链表,用以为数组的方式来表示链表,因此拥有了数组的特性:1、内存连续 2、存储的数据类型相同,作用是为了不使用指针来表示连续存储的链式数据结构。
        它允许我们不改变各元素的物理位置,只要重新链接就能够改变这些元素的逻辑顺序。它是利用数组定义的,在整个运算过程中存储空间的大小不会变化。静态链表的每个结点由两个数据成员构成:data域存储数据,next域存放链接指针。所有结点形成一个结点数组,它也可以附加头结点。插入和删除的时间复杂度都是 O(n)。

图示静态链表的存放数组为A,则链表的附加头结点在A[0],从A[1]起,后面都是链表结点的存放控件,链接指针用数组元素的下标(序号)表示。A[0].next给出链表第一个结点的位置,依次寻找接下来的逻辑结点位置。当找到某一结点A[k],A[k].next = -1,则链表终止。由于A[0]有效,所以空指针NULL用-1表示。

#include<iostream>
using namespace std;
#define Maxsize 10
#define Elemtype int
#include"ctime";
#include"cstdlib";

typedef struct {
	Elemtype data;
	int next;
}SLinkList[Maxsize];

//初始化
void Initlist(SLinkList& L)
{
	for (int i = 0; i < Maxsize+1; i++)
	{
		L[i].data = 0;
		L[i].next = -2;
	}
	L[0].next = -1; //表示表尾
}

bool Value(SLinkList& L)
{
	int last = 0; //表尾
	int n;
	cout << "(最多10个)你想创建的元素个数:" << endl;
	cin >> n;
	srand((unsigned)time(NULL));// 设置种子

	for (int i = 0; i < n + 1; i++)
	{
		int address = (rand() % Maxsize) + 1; //地址指针,但其实是数组的下标
		int number = rand() % 100; //存放的数据
		if (i == 0)
		{
			L[0].next = address;
			L[0].data = 0;
			last = address;
		}
		else if (i == n)
		{
			L[last].next = -1;  //代表来到表尾
			L[address].data = number;
		}
		else
		{
			L[last].next = address; //存放下一个结点的“指针”
			L[last].data = number;
			last = address;
		}
	}
	return true;
}

//遍历整个数组
void TraverseList(SLinkList& L) {
	for (int i = 0; i < Maxsize+1; i++) {
		if (L[i].next != 0) {
			cout << "第" << i << "个:" << L[i].data << " " << L[i].next << endl;
		}
	}
}

int main()
{
	SLinkList L;
	Initlist(L);
	Value(L);
	TraverseList(L);

	system("pause");
	return 0;
}

        静态链表的定义,实际上是一个 MaxSize 长度的结构体数组,以此来模拟链表,要寻找某个结点只能从头遍历,真的非常麻烦。在不支持指针的低级语言中,静态链表十分适合;或者适用于数据元素容量固定不变的场景(例如操作系统的文件分配表 FAT)。其他情况下还是使用动态链表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值