目录

数据结构链表

数据结构:链表

链表的概念:

简单来说:使用一条链子连接起来一些节点,使用指针的方式将一些数据连接起来

  • 链表是一种物理存储单元上非连续、非顺序的存储结构
  • 逻辑顺序是通过链表中的指针链接次序实现的

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

链表的特点:

  1. 存储空间可连续也可不连续
  2. 链表的存取通过头节点开始
  3. 是非随机存取的存储结构(需要通过指针的方式一个个访问)

链表和顺序结构:

顺序表链表
存储空间存储空间是连续的存储空间可连续也可不连续
查找元素时间复杂度O(logN)或O(1)或O(N)时间复杂度O(N)
删除元素需要移动元素O(N)不需要移动元素 O(1)
插入元素需要移动元素O(N)不需要移动元素 O(1)
  • 查找:顺序表速度快
  • 插入和删除: 链表快

单链表:

单链表节点:

  • 数据:elem
  • 指向下一个节点的指针:next

https://i-blog.csdnimg.cn/direct/761056c5eefd42a383b6684cdf64f408.png

单链表结构:

  • 需要一个头节点
  • top节点指向头节点
  • Size 统计节点个数

https://i-blog.csdnimg.cn/direct/e60cfa29fb7c4c0aa33245ab783d4841.png


struct List_node{//链节点 
	int elem=0;//数据 
	List_node* next=nullptr;//下一个节点的指针 
}; 

struct List{//链表 
	int Size;//保存节点个数
	List_node* head;//头结点
	List_node* top;//头指针 
}; 
List list;//创建链表
void Initialize(){//链表初始化 
	list.head=new List_node;
	list.head->elem=0;
	list.head->next=nullptr;
	list.Size=0;
	list.top=list.head;//头指针指向头节点 
} 

单链表基本操作:

  • size() 获取节点个数
  • empty() 判断是否为空链表
  • push_front(data) 头部插入
  • push_back(data)尾部插入
  • pop_front() 头部删除
  • pop_back()尾部删除
  • erase(data)删除某个数据
  • traverse()遍历链表
size()元素个数

int size(){
	return list.Size;
} 
empty()判空

bool empty(){
	if(list.Size==0)return true;
	return false;
	//或
	if(list.top==list.head) return true;
	return false; 
}
push_front(data)头部插入

头部插入

  • 头插法:插到头结点的前面,让新的节点变为头结点
  • 尾插法:插到头结点的后面 (这个常用一点)

头插法:

https://i-blog.csdnimg.cn/direct/369097bf39774754a39d7dfa67ea244a.png


void push_front_head(int data){//头插法 
	List_node * newnode=new List_node;
	newnode->elem=0; 
	list.top->elem=data;//保存值 
	newnode->next=list.top; //next 指向top指向的位置 
	list.top=newnode;//top指向新节点 
    list.Size++;//元素个数+1
}

尾插法:

https://i-blog.csdnimg.cn/direct/d842674ddafa45f0ab5be56a98b8e3da.png


void push_front_tail(int data){ //尾插法 
	List_node * newnode=new List_node;
	newnode->elem=data;
	newnode->next=list.top->next; 
	list.top->next=newnode;
    list.Size++;//元素个数+1
} 
push_back(data)尾部插入
  • 创建一个新节点,保存数据
  • 再创建一个指针遍历链表,找到最后一个节点
  • 然后将节点插入到最后一个节点后面

https://i-blog.csdnimg.cn/direct/688dc463f5d3426f8232898ede321689.png


void push_back(int data){//尾部插入 
	List_node* new_node=new List_node;
	new_node->elem=data;
	//开始找最后一个节点
	List_node* p=list.top;
	int num=list.Size;
	while(num>0){
		p=p->next;//指向下一个节点
		num--; 
	}
	//找到最后节点位置,开始插入
	p->next=new_node; 
} 
pop_front()头部删除
  • 先创建一个指针指向第一个节点(保存好第一个节点)
  • top指向第一个节点的下一个节点
  • 释放第一个节点的内存

void pop_front(){
	if(list.Size==0) cout<<"链空"<<endl;
	else{
		List_node* node=list.top->next;//指向第一个节点
		list.top=node->next;//top指向第二个节点
		delete node;//释放第一个节点的内存 
		list.Size--;//元素个数-1 
	} 
}
pop_back()尾部删除
  • 首先要创建两个指针 第一个指向top 第二个指向top->next
  • 当第二个节点的next==nullptr时说明是最后一个节点了
  • 然后将倒数第二个指针的next设置为nullptr
  • 释放最后一个节点内存

void pop_back(){
	if(list.Size==0) cout<<"链空"<<endl;
	else{
		List_node* node1=list.top;//指向top
		List_node* node2=list.top->next;//指向第一个节点
		while(node2->next!=nullptr){
			node1=node1->next;
			node2=node2->next;
		}
		//到达最后的位置
		node1->next=nullptr;//将倒数第二个节点的next 置为空
		delete node2;//释放最后一个节点的内存
		list.Size--;//元素个数-1 
	}
} 
erase(data)删除指定数据节点
  • 首先先遍历数组找到指定的数据的位置和其前一个节点的位置
  • 将前一个节点的next指向该节点的下一个节点
  • 释放这个节点的内存

void erase(int data){
	if(list.Size==0) cout<<"链空"<<endl;
	else{
		List_node* node1=list.top;//指向top
		List_node* node2=list.top->next;//指向第一个节点
		if(node2->elem==data) pop_front();//如果是第一个节点的话直接删除
		else{
			bool b=false;//表示是否找到该数据的位置 
			while(node2->next!=nullptr){
				node1=node1->next;
				node2=node2->next;
				if(node2->elem==data){
					b=true;
					break;
				} 
			}
			//找到位置
			if(b){
				node1->next=node2->next;
				delete node2;//释放最后一个节点的内存
			    list.Size--;//元素个数-1 
			} 
            else cout<<"未找到这个数据的节点";
		} 	
	}
} 
traverse()遍历输出链表
  • 方法1:使用元素的个数进行遍历
  • 方法2:使用node->next != nullptr 进行遍历

void traverse(){
	//方法1: 
	if(list.Size==0)cout<<"空链表"<<endl;
	else{
		List_node* node=list.top->next;//指向第一个节点
		int num=list.Size;
		while(num>0){
			cout<<node->elem<<" ";//输出数据
			num--; 
		} 
	}
	//方法2: 
	if(list.Size==0)cout<<"空链表"<<endl;
	else{
		List_node* node=list.top->next;//指向第一个节点
		while(node->next!=nullptr){
			cout<<node->elem<<" ";//输出数据
			node=node->next; 
		} 
	}
	
}
总程序:

struct List_node{//链节点 
	int elem=0;//数据 
	List_node* next=nullptr;//下一个节点的指针 
}; 

struct List{//链表 
	int Size;//保存节点个数
	List_node* head;//头结点
	List_node* top;//头指针 
}; 
List list;//创建链表
void Initialize(){//链表初始化 
	list.head=new List_node;
	list.head->elem=0;
	list.head->next=nullptr;
	list.Size=0;
	list.top=list.head;//头指针指向头节点 
} 

int size(){
	return list.Size;
} 
bool empty(){
	if(list.Size==0)return true;
	return false;
	//或
	if(list.top==list.head) return true;
	return false; 
}

void push_front_head(int data){//头插法 
	List_node * newnode=new List_node;
	newnode->elem=0; 
	list.top->elem=data;//保存值 
	newnode->next=list.top; //next 指向top指向的位置 
	list.top=newnode;//top指向新节点 
}

void push_front_tail(int data){ //尾插法 
	List_node * newnode=new List_node;
	newnode->elem=data;
	newnode->next=list.top->next; 
	list.top->next=newnode;
} 

void push_back(int data){//尾部插入 
	List_node* new_node=new List_node;
	new_node->elem=data;
	//开始找最后一个节点
	List_node* p=list.top;
	int num=list.Size;
	while(num>0){
		p=p->next;//指向下一个节点
		num--; 
	}
	//找到最后节点位置,开始插入
	p->next=new_node; 
} 

void pop_front(){
	if(list.Size==0) cout<<"链空"<<endl;
	else{
		List_node* node=list.top->next;//指向第一个节点
		list.top=node->next;//top指向第二个节点
		delete node;//释放第一个节点的内存 
		list.Size--;//元素个数-1 
	} 
}

void pop_back(){
	if(list.Size==0) cout<<"链空"<<endl;
	else{
		List_node* node1=list.top;//指向top
		List_node* node2=list.top->next;//指向第一个节点
		while(node2->next!=nullptr){
			node1=node1->next;
			node2=node2->next;
		}
		//到达最后的位置
		node1->next=nullptr;//将倒数第二个节点的next 置为空
		delete node2;//释放最后一个节点的内存
		list.Size--;//元素个数-1 
	}
} 

void erase(int data){
	if(list.Size==0) cout<<"链空"<<endl;
	else{
		List_node* node1=list.top;//指向top
		List_node* node2=list.top->next;//指向第一个节点
		if(node2->elem==data) pop_front();//如果是第一个节点的话直接删除
		else{
			bool b=false;//表示是否找到该数据的位置 
			while(node2->next!=nullptr){
				node1=node1->next;
				node2=node2->next;
				if(node2->elem==data){
					b=true;
					break;
				} 
			}
			//找到位置
			if(b){
				node1->next=node2->next;
				delete node2;//释放最后一个节点的内存
			} 
			list.Size--;//元素个数-1 
		} 	
	}
} 

void traverse(){
	//方法1: 
	if(list.Size==0)cout<<"空链表"<<endl;
	else{
		List_node* node=list.top->next;//指向第一个节点
		int num=list.Size;
		while(num>0){
			cout<<node->elem<<" ";//输出数据
			num--; 
		} 
	}
	//方法2: 
	if(list.Size==0)cout<<"空链表"<<endl;
	else{
		List_node* node=list.top->next;//指向第一个节点
		while(node->next!=nullptr){
			cout<<node->elem<<" ";//输出数据
			node=node->next; 
		} 
	}
	
}

STL中的单链表:

  • 头文件:#include<forward_list> (C++11添加)
  • 关键词:forward_list

forward_list 是一个容器,支持在容器中的任何位置快速插入和删除元素。

不支持快速随机访问。它实现为单向链表。

forward_list的函数:

构造函数:
函数说明
forward_listlist默认构造形式
forward_list list(beg,end)将[beg,end)的元素拷贝给本身
forward_list list(n)构造函数将n个默认数值给本身
forward_list list(n,elem)构造函数将n个elem拷贝给本身
forward_list list(const deque&dqe)拷贝构造
赋值操作:
函数说明
forward_list&operator=(const forward_list&dqe)重载=号
assign(beg,end)将[beg,end)中的数据拷贝到本身
assign(n,elem)将n个elem 赋值给本身
assign({数据})使用 {} 进行赋值
大小操作:
函数说明
max_size()返回容器所能包含元素个数的最大值
empty()判空
swap(dq1,dq2)可以交换两个容器数据
resize(num)重新指定容器长度 为num 变短删除多余元素
resize(num,elem)重新指定容器长度 为num 变短删除多余元素,变长用elem添加
头部插入删除:
函数说明
push_front(elem)头部添加一个元素
emplace_front()头部添加一个元素(效率高)
pop_front()头部删除一个元素
插入和删除:
函数说明
insert_after()位置中插入数据,返回新数据的位置
splice_after()将某个 forward_list 容器中指定位置或区域内的元素插入到另一个容器的指定位置之后。
emplace_after()位置中插入数据,返回新数据的位置
erase_after()删除位置的数据 返回下一个数据的位置
clear()清空
数据获取:
函数说明
front()返回第一个元素
其他操作:
函数说明
merge归并两个已排序链表
remove()删除指定条件的元素
reverse()逆转链表
unique()移除重复元素
sort()排序

#include<iostream>
#include<forward_list>
using namespace std;
int main(){
	
	//构造函数 
	forward_list<int> n;//创建一个单链表
	//使用数组构造
	int arr[10]={1,2,3,4,5,6,7,8,9,10};
	forward_list<int> n1(arr,arr+10);
	forward_list<int> n2(arr+2,arr+10);
	forward_list<int> n3(begin(arr),end(arr));
	//使用数据构造
	forward_list<int> n4(10);//10个默认数据 
	forward_list<int> n5(10,500);//10个数据的值为500
	//使用拷贝构造
	forward_list<int> n6(n5);//拷贝
	
	//赋值操作
	forward_list<int> n7=n6;//使用=赋值 
	n7.assign({1,2,3,4,5});//使用assign赋值 
	n7.assign(10,100);//使用assign赋值	
	n7.assign(n2.begin(),n2.end());//使用assign赋值
	
	//访问数据
	cout<<n7.front()<<endl;//访问第一个节点 
	
	//头部插入和删除
	n7.push_front(50);//头部插入
	n7.emplace_front(60);//头部插入
	n7.pop_front();//头部删除
	
	//遍历链表
	for(auto it=n7.begin();it!=n7.end();it++){
		cout<<*it<<" ";
	} 
	 
	return 0;
}

forward_list的迭代器:

由单链表只能从前向后遍历,不支持反向遍历,因此 forward_list 容器只提供前向迭代器

  • 只能从前往后遍历 可以++  但不能–
迭代器说明
before_begin()返回一个前向迭代器,其指向容器中第一个元素之前的位置。
begin()返回一个前向迭代器,其指向容器中第一个元素的位置。
end()返回一个前向迭代器,其指向容器中最后一个元素之后的位置。
cbefore_begin()和 before_begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
cbegin()和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
cend()和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。

循环单链表:

  • 循环链表是与单链表一样,是一种链式存储结构,
  • 但循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。
  • 在判断是否到表尾时,判断该结点的next的值是否是表头结点,当next值等于表头指针时,说明已到表尾。

节点和链表结构:

https://i-blog.csdnimg.cn/direct/4b139c48ae1743b085dc2ba4f404e998.png


struct List_node{ //链表节点 
	int elem=0;
	List_node *next=nullptr;
};

struct Circular_Linked_List{//循环单链表 
	int Size;
	List_node *head;//头节点 
	List_node *top;////头指针 
};
Circular_Linked_List cll;//创建一个循环单链表 
void Initialize(){ //循环单链表初始化 
	cll.Size=0;//元素个数 
	cll.head=new List_node;
	cll.top=cll.head;//头指针指向头节点
	cll.head->next=cll.head;//尾部连接起来,实现循环 
}

循环单链表的基本操作:

  • size()元素个数
  • empty()判空
  • push_front(data)头部插入
  • pop_front()头部删除
  • front()返回头部元素

https://i-blog.csdnimg.cn/direct/1f3517eb43664919a11bb3404e35ffd3.png

size()元素个数

int size(){
	return cll.Size;
} 
empty()判空
  • 方法1:直接判断Size==0
  • 方法2:判断 top==head->next

bool empty(){
	//方法1
	if(cll.Size==0) return true;
	return false;
	//方法2
	if(cll.top==cll.head->next) return true
	return false; 
}
push_front(data)头部插入
  • 创建一个新节点 newnode
  • newnode->next指向top->next
  • top->next=newnode

void push_front(int data){
	List_node*newnode=new List_node;
	newnode->elem=data;
	newnode->next=cll.top->next;
	cll.top->next=newnode; 
	cll.Size++;
}
pop_front()头部删除
  • 创建一个新节点newnode;
  • newnode指向第一个节点top->next;
  • top->next=newnode->next; 连接到第二个节点
  • delete newnode;释放内存

void pop_front(){
	if(empty()) cout<<"空链"<<endl;
	else{
		List_node*newnode=new List_node;
		newnode=cll.top->next;
		cll.top->next=newnode->next; 
		delete newnode;
		cll.Size--;
	} 
} 
front()返回头部元素

int front(){
	if(empty()) cout<<"空链"<<endl;
	else return cll.top->next->elem;
}

双向链表:

在双向链表中,结点除含有数据域外,还有两个链域:

  • 一个存储直接后继结点地址,一般称之为右链域
  • 一个存储直接前驱结点地址,一般称之为左链域
  • 可以进行双向的移动

双向链表的结构:

https://i-blog.csdnimg.cn/direct/3cc34312348f445187b26dd95fd989da.png

https://i-blog.csdnimg.cn/direct/8eb595e295e846cdb508b48f47b51238.png


struct List_node{ //链表节点 
	List_node* prev=nullptr;
	int elem=0;
	List_node *next=nullptr;
};

struct Doubly_Linked_List{//双向链表 
	int Size;
	List_node *head;//头节点 
	List_node *top;//头指针 
};

Doubly_Linked_List dll;//创建一个链表 

void Initialize(){ //双向链表初始化 
	dll.Size=0;//元素个数 
	dll.head=new List_node;
	dll.top=dll.head;//头指针指向头节点
}

双向链表的操作:

  • size()节点个数
  • empty()判空
  • push_front() 头部添加
  • pop_front() 头部删除
  • insert(number,data) 插入
  • erase(number)删除
size()节点个数

int size(){
	return dll.Size;
} 
empty()判空

bool empty(){
	//方法1
	if(dll.Size==0) return true;
	else false;
	//方法2
	if(dll.top->next==nullptr) return true;
	return false; 
}
push_front(data) 头部添加
  • 首先需要创建一个新节点保存数据
  • 新节点的next指针指向第一个节点  第一个节点的前驱指向新节点
  • 新节点的前驱指向top节点  top的后继指向新节点

void push_front(int data){
	List_node * newnode=new List_node;
	newnode->elem=data;
	newnode->next=dll.top->next;
	dll.top->next->prev=newnode;
	dll.top->next=newnode;
	newnode->prev=dll.top;
	dll.Size++;	
}
pop_front() 头部删除
  • 首先将第二个节点的前驱指向 top
  • 再将top的后继指向第二个节点

void pop_front(){
	if(empty()) cout<<"链空"<<endl;
	else{
		dll.top->next->next->prev=dll.top;
		dll.top->next=dll.top->next->next;
		dll.Size--;
	}
}
insert(number,data)插入
  • 首先先创建一个节点s存储需要存入的数据
  • 再创建一个指针 p指向需要插入的位置
  • 将s节点的prev指向p的prev
  • 将s节点的next指向p
  • 将p的prev->next指向s节点
  • 将p节点的prev指向s节点

https://i-blog.csdnimg.cn/direct/262cfb20b87a49939a91a402cdc2310e.png


void insert(int inedex,int data){
	if(empty()) cout<<"链空"<<endl;
	else{
		//开始查找
		List_node *p=dll.top->next;
		while(p!=nullptr){
			if(p->elem==data) break;//找到了退出循环
			else p=p->next;//进入下一个节点 
		} 
		if(p!=nullptr){
			List_node*newnode=new List_node;
			newnode->elem=data;
			//开始插入到p前面
			newnode->prev=p->prev; 
			newnode->next=p;
			p->prev->next=newnode;
			p->prev=newnode;
			dll.Size++;
		} 
	} 
}
erase(int data)删除
  • 首先创建一个节点p指向要删除的节点
  • p->prev->next=p->next;
  • p->next->prev=p->prev;
  • delete p;释放内存

https://i-blog.csdnimg.cn/direct/488801a322344b8986318923181075c2.png


void erase(int number){ //查找这个number所在的位置
	if(empty()) cout<<"链空"<<endl;
	else{
		//开始查找
		List_node *p=dll.top->next;
		while(p!=nullptr){
			if(p->elem==number) break;//找到了退出循环
			else p=p->next;//进入下一个节点 
		} 
		if(p!=nullptr){
			//开始删除 
			p->prev->next=p->next;
			p->next->prev=p->prev
			delete p; 
			dll.Size--;
		} 
	} 
}

STL双向链表:

  • 头文件:#include
  • 关键词:list

list 容器,又称双向链表容器,即该容器的底层是以双向链表的形式实现的。这意味着,list 容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。

  • list中的迭代器只支持前前移和后移 属于双向迭代器

优点:

  • 采用动态存储分配 不会造成内存浪费和溢出
  • 插入和删除方便

缺点:

  • 空间(指针域)和时间(遍历)耗费大
  • list 的插入和删除操作会造成原有迭代器的失效
  • 不能通过位置直接访问元素

https://i-blog.csdnimg.cn/direct/29c9ccb5b6464d7d86df9716952055bf.png

list常用函数:

构造函数:
函数说明
listlt;模板类实现
list lt(beg,end)[beg,end)区间拷贝给本身
list lt(n)n个默认值的元素
list lt(n,elem)将n个elem 拷贝给本身
list lt(const list &lst)拷贝构造函数,元素类型需要一致
赋值操作:
函数说明
assign(beg,end)[beg,end)的数据靠内给本身
assign(n,elem)n个elem拷贝给本身
list&operator=(const list &lit)重载=
容量大小
函数说明
size()返回元素个数
empty()判空
resize(num)重新指定长度为num ,容器变短多余元素删除
resize(num, elem)重新指定长度为num ,变长用elem填充容器变短多余元素删除
插入操作:
函数说明
push_back(elem)尾部加入一个元素
emplace_back()尾部加入一个元素(效率高)
push_front(elem)头部插入一个元素
emplace_front(elem)头部插入一个元素(效率高)
emplace(iterator,elem)在容器中的指定位置插入元素(效率高)
insert(iterator,n,elem)在迭代器位置插入n个elem 无返回值
insertiterator,beg,end)在迭代器位置插入[beg,end)区间的数据,无返回值
splice(iterator , list& x)将其他 list 容器存储的多个元素添加到当前 list 容器的指定位置处
删除操作:
函数说明
pop_back()删除最后一个元素
pop_front()头部移除一个元素
erase(beg,end)删除[beg,end)中的数据 返回下一个数据
erase(iterator)删除迭代器位置的数据 返回下一个数据
remove(elem)删除容器中与elem相等的数
clear()清空
remove_if(functional)删除容器中满足条件的元素。
取出数据:
函数说明
front()返回第一个元素
back()返回最后一个元素
其他函数:
函数说明
reverse()列表数据反转
sort()对数据进行排序
swap()交换

#include<iostream>
#include<list>
using namespace std;
int main(){
	
	//构造函数
    list<int> lt;//没有元素的list
	int a[10]={1,2,3,4,5,6,7,8};
	list<int> lt2(begin(a),end(a));//使用迭代器创建
	list<int> lt3(10,3);//10个3创建list
	list<int> lt1(10);//10个默认值元素的list 
	list<int>lt4(lt3);//拷贝容器 
	
	//赋值操作 
	lt4.assign(10,6);//将10个6拷贝给lt4 
	lt4=lt3;//重载= 将lt3赋值给lt4 
	lt3.swap(lt2);//交换数据 
	
	//容量大小
	if(lt.empty()) cout<<"容器为空"<<endl;
	cout<<lt.size()<<endl;//输出元素个数
	lt.resize(10);//设置list长度为10,不够使用默认值填充
	lt.resize(20,3);//设置list长度为20,不够用3填补	  
	
	//插入操作
	lt.push_back(30);//尾部添加元素
	lt.emplace_back(60);//尾部添加元素
	lt.push_front(80);//头部插入元素
	lt.emplace_front(90);//头部插入元素
	 
	auto p=++lt.begin();//获取迭代器 
	lt.emplace(p,10);//指定位置插入 
	lt.insert(p,10,5);//指定位置插入10个5 
	lt.insert(p,begin(a),end(a));//使用迭代器选择插入内容 
	
	//删除操作
	
	lt.pop_back();//删除最后一个元素
	lt.pop_front();//删除第一个元素
	
	lt.erase(lt.begin(),lt.end());//删除区域中的元素
	lt.erase(p);//删除指定位置元素 
	
	lt2.remove(20);//删除20这个数,会删除全部20 
	lt2.remove_if([](int a){ //删除大于5的数据 
		return a>5;
	});
	lt.clear();//清除所有内容 
	
	//访问数据
	cout<<lt.front()<<endl;//返回第一个元素 
	cout<<lt.back()<<endl; //返回最后一个元素  
	
	
	return 0;
}

双向循环链表:

双向链表首尾相连就是双向循环链表

  • 头结点的前驱指向自己
  • 头结点的后继指向自己

双向循环链表的结构:

https://i-blog.csdnimg.cn/direct/192684a55af24eccb8c7520fb8f13fa2.png


struct List_node{ //链表节点 
	List_node* prev=nullptr;
	int elem=0;
	List_node *next=nullptr;
};

struct Doubly_Circular_Linked_List{//双向链表 
	int Size;
	List_node *head;//头节点 
	List_node *top;//头指针 
};

Doubly_Circular_Linked_List dcll;//创建一个链表 

void Initialize(){ //双向链表初始化 
	dcll.Size=0;//元素个数 
	dcll.head=new List_node;
	dcll.top=dll.head;//头指针指向头节点
    dcll.head->next=dcll.top;//指向本身
    dell.head->prev=dcll.top;//指向本身
}

功能请自行实现

链表的基本使用:

反转单链表:

将  1->2->3->4->5  反转成 5->4->3->2->1

从头开始一个链一个链进行拆解

  • 由于拆的是next指针  拆完后会指向其他内存 所以需要提前保存好下一个节点

https://i-blog.csdnimg.cn/direct/5a326d19370d47ebb2bad3bb0f423f9c.png


void ReverseList(List_node*head){
	if(head==nullptr) return;
	//创建 P H K 指针
	List_node *P,*H,*K;
	P=nullptr;
	H=head->next;//指向第一个节点
	K=H->next;//指向H下一个节点
	while(H!=nullptr){
		H->next=P;
		P=H;
		H=K;
		K=K->next;
	} 
	//最后头结点指向p
	head->next=P; 
}

判断单链表是否有环

使用快慢指针的方法判断有没有环

  • 创建一个指针为慢指针P  每次走1步
  • 创建一个指针为快指针K****每次走2步
  • 让两个指针从同一起点出发 如果两个点会相遇的话 那就是有环
  • 如果 K==nullptr 说明没有环

https://i-blog.csdnimg.cn/direct/524102a39e5b4f1682ba518918dbc77a.png


bool hasCycle(List_node*head){ //head为头结点  head->next 为第一个数据 
	if(head->next==nullptr) return;
	List_node*P=head->next;//指向第一个数据 慢指针
	List_node*K=head->next;//指向第二个数据 快指针
	while(K!=nullptr||K->next!=nullptr){
		if(P==K) return true;
		P=P->next;//前进1步 
		K=K->next->next;//前进2步 
	}
	return false; 
}

查找单链表环的入口节点

  • 首先需要判断链表是否有环
  • 如何判断环入口节点呢?

https://i-blog.csdnimg.cn/direct/88becd2ed54748a88566a5cfb015482d.png


List_node* EntryNodeOfLoop(List_node*head){ //head为头结点  head->next 为第一个数据 
	if(head->next==nullptr) return;
	List_node*P=head->next;//指向第一个数据 慢指针 
	List_node*K=head->next;//指向第二个数据 快指针 
	while(K!=nullptr||K->next!=nullptr){
		if(P==K){ //如果有环 
			List_node* G=head->next;//起点 
			while(G!=P){
				G=G->next;
				P=P->next; 
			} 
			//到达相遇点
			return G; 
		}
		P=P->next;//前进1步 
		K=K->next->next;//前进2步
		 
	}
	return nullptr; 
}
 

链表运用:

队列安排:

https://i-blog.csdnimg.cn/direct/f94f0bd011ef44ac9281450de60b4282.png

https://i-blog.csdnimg.cn/direct/dac2bccfb76b411fa9fb7d153143222e.png

https://i-blog.csdnimg.cn/direct/9c97fbc3f8fb4e7c8383a118bb0b379a.png

题目分析:

就是现在有 n个人进行排队,排好队后会删除对应学号的某些同学,第一个人是直接进入队中,从第二个人开始,可以插入到队列中已有学员中某个学员的左边(0)或右边(1)

  • 题目的运行过程主要都是针对数据的插入
  • 使用顺序表的话对于插入操作效率还是很低的
  • 所以这里考虑使用链表进行解题

由于数据要么在某个人的左边或右边,所以我们要能获取某个节点的前驱和后继

  • 使用链表时就不考虑单链表,单链表无法获取前驱
  • 这里使用list(双向链表)
  • 我们要插入数据,可以提前存储好节点的迭代器(相当于指针)
  • 可以使用prev() 获取这个迭代器的前驱(左边)
  • 可以使用next() 获取这个迭代器的后继(右边)
  • 插入数据可以直接使用insert()进行插入
  • 最后删除的话使用erase()进行删除,可能会有重复数据我们可以使用一个数组存储是否被删除过
相应程序:

#include<iostream>
#include<list>
#include<iterator>
using namespace std;

using Iter = list<int>::iterator;
const int max_N=1e5+10;
Iter pos[max_N];//存储位置迭代器
list<int> lt;
bool b[max_N];//表示有没有被删除
int N; 
int main(){
	cin>>N;
	lt.push_front(1);
	pos[1]=lt.begin();//添加迭代器
	for(int i=2;i<=N;i++){
		int k,p;
		cin>>k>>p;
		if(p==0){
			pos[i]=lt.insert(pos[k],i);//左边插入数据,indsert会返回当前插入数据的迭代器
		}
		else{
			auto next_It=next(pos[k]);//获取下一个迭代器位置
			pos[i]=lt.insert(next_It,i);//右边插入数据 
		} 	
	} 
	int M;
	cin>>M;
	for(int x,i=1;i<=M;i++){
		cin>>x;
		if(!b[x]) lt.erase(pos[x]);
		b[x]=true;
	} 
	for(auto p:lt){
		cout<<p<<" ";
	}
	return 0;
}