1. 单链表的概念
用链式储存结果存储数据的线性表,是为链表
链式存储,意思就是逻辑上相邻的数据,物理位置不一定相邻
因此需要指针
可以给每个元素附加上一个指针,这个指针的作用,就是作为下一个元素的地址,指向下一个元素
所以链表中的每个节点,包含两个域:数据域和指针域
数据域存储数据元素,指针域存储下一个节点的地址
注意!!!指针域存储的地址,是下一个节点的地址,而并非下一个数据元素的地址,you feel me?
链表的第一种,称之为单向链表,简称单链表,就是一个个地指向下一个
首先定义节点,利用结构体语法自定义新数据类型——节点
以下这种定义方法是很规范很专业的定义法
这种声明写法,专门用于在结构中体当中,包含指向它自己的指针!
因为我们不仅需要节点,还需要节点指针
因此这种写法是必须要掌握的
关于细节上的理解,可以参阅这篇文章————https://blog.csdn.net/m0_37973607/article/details/78900184
typedef int Element_Type;
typedef struct Linked_List_Node{
Element_Type data;
//j节点指针
struct Linked_List_Node* next_node_address;
}Linked_List_Node, *Linked_List, *Node_Address;
到此就定义好了单链表的节点,节点这一数据结构定义好了之后,就可以着手下一步的工作——链接各个节点,形成单链表
为了保持一般性,我们设置一个头节点。头节点不存放数据(但可以存放其他信息,比如表长),但有一个头指针。
这个头指针存储着链表第一个节点的地址,因此这样就可以找到链表中的每个节点
“带有头节点的链表就像是给铁链加了钥匙扣”
单链表的缺点,在于访问元素速度慢,因为顺序表可以瞬间找到任意一个元素(称为随机存取,random access)
但单链表若要找某个元素(第i个元素),只能从头开始一个一个地找(称为顺序存取,sequential access)
2. 单链表的基本操作
定义完单链表,就要定义相关的操作
2.1. 初始化
初始化,就是构建一个空的单链表,可以想见,就是创建一个头节点,然后令其指针域为空
实现
bool Initialize_Linked_List(Linked_List &lineked_list1)
{
lineked_list1 = new Linked_List_Node;
if(!linked_list1)
{
cout<<“Fail to generate a Node!”<<endl;
return false;
}
linked_list1->next = NULL;
return true;
}
2.2 创建
创建单链表有两种方法
- 头插法
- 尾插法
头插法比较不容易理解,但归结为一句话,就是每次生成一个新节点,就把新节点插到头节点之后,就有点像栈一样
所以称为头插法,新进来的就直接插到头节点之后,成为第一节点
步骤
- 初始状态,此时只有一个头节点
- 输入一个数据元素,创建新节点,把数据元素放到新节点的数据域
new_node_address = new Linked_List_Node;
cin>>new_node_address->data;
- 头插操作,就是让头节点的地址域等于第一个节点的地址
即
new_node_address->next_node_address = linked_list1->next_node_address;
//linked_list1 实际上也是头节点的地址
linked_list1->next_node_address = new_node_address;
- 再创建一个新节点
new_node_address2 = new Linked_List_Node;
cin>>new_node_address2->data;
- 再进行头插操作
new_node_address2->next_node_address = linked_list1->next_node_address;
linked_list1->next_node_address = new_node_address2;
总的代码实现
void Create_Linked_List_H(Linked_List &new_linked_list)
{
int length_of_list;
Node_Address new_node_address;
new_linked_list = new Linked_List_Node;
new_linked_list->next = NULL;
cout<<"Input the length of the linked list n: "<<endl;
cin>>length_of_list;
cout<<"Using the method of head insertion to create the linked list."<endl;
cout<<"enter each element of the linked list in order:"<<endl;
while(length_of_list--)
{
new_node_address = new Linked_List_Node;
cin>>new_node_address->data;
new_node_address->next_node_address = new_linked_list->next_node_address;
new_linked_list->next_node_address = new_node_address;
}
}
尾插法步骤也比较好理解,就是每次把新节点链接链表的尾部
因此不同的是,这次我们需要一个为尾指针(数据类型为Node_Address),尾指针的作用在于,它始终代表当前链表的最后一个节点
因为它的值始终是最后一个节点的地址
(我不在乎谁是县长,我只想当县长夫人)
步骤
- 初始状态,只有一个头节点。再设置一个尾指针,指向头节点(头节点不存储数据,此时它头尾兼是)
- 输入数据元素,创建新节点,把数据元素放入新节点的数据域
new_node_address = new Linked_List_Node;
cin>>new_node_address->data;
- 尾插操作
········
代码实现
void Create_Linked_List_R(Linked_List &linked_list)
{
int length;
linked_list = new Linked_List_Node;
Node_Address tail_pointer;
Node_Address new_node_address;
linked_list->next_node_address = NULL;
//一开始,头节点也是尾节点
tail_pointer = linked_list;
cout<<"Input the length of the linked list n: "<<endl;
cin>>length_of_list;
cout<<"Using the method of reversed insertion to create the linked list."<endl;
cout<<"enter each element of the linked list in order:"<<endl;
while(length_of_list--)
{
new_node_address = new Linked_List_Node;
cin>>new_node_address->data;
//新节点作为新的尾节点,必须指向NULL
new_node_address->next_node_address = NULL;
//将新节点插到原有的尾节点之后(理解)
tail_pointer->next_node_address = new_node_address;
//旧的尾节点变成了倒数第二节点,现在把插进来的新节点变成新的尾节点
tail_pointer = new_node_address;
}
}
2.3 取值
要想找到第i个节点,必须从头节点开始,然后第一节点···直到第i个节点
步骤
- 先顶一个查找指针get_pointer,指向第一个元素节点,用counter做计数器,counter = 1
- 如果p is not empty 且目标节点位置i>j,则get_pointer 指向下一个节点,然后counter++,
即get_pointer= get_pointer->next_node_address;
counter++; - 若get_pointer为空或者j=i时停止。get_pointer为空,代表i的值大于整个链表的长度,因此并不存在d第i个节点
j=i,则代表找到了
实现
bool Get_Element_of_List(Linked_List &linked_list, int target_pos, int &element)
{
int counter = 1;
Node_Address get_pointer;
get_pointer = linked_list->next_node_address;
while(counter<target_pos && get_pointer)
{
get_pointer = get_pointer->next_node_address;
counter++;
}
//如果实际链表没有这么长或者输入的编号不合法(我要取第-1个节点)
if(!get_pointer || counter>target_pos)
{
cout<<"invalid target position!"<<endl;
return false;
}
element = get_pointer->data;
return true;
}
2.4 查找
在一个链表中寻找是否存在某个元素,可以定义一个search_pointer,还是指向元素第一个节点,然后就是比较查找法
代码实现
bool Search_Target_in_List(Linked_List &linked_list, int target)
{
//标准的写法
Node_Address search_pointer;
search_pointer = linked_list->next_node_address;
//还未遍历到最后一个元素时 且 还没找到时
while(search_pointer && (search_pointer->data != target))
{
search_pointer = search_pointer->next_node_address;
}
//不在里面
if(!search_pointer)
{
cout<<"Target is not in the linked list!"<<endl;
return false;
}
//确实在里面
return true;
}
2.5 插入
我们希望在第i个节点之前插入一个新的节点,这是我们的想法
就是说:我们要让我们想输入的节点成为第i个节点,而原来的第i个节点成为第i+1个节点
但我们没办法直接在第i个节点之前插入,只能寻找 其等价形式————在第i-1个节点之后插入我们的节点
并让我们的节点的指针域指向原来的第i个节点
bool Insert_an_Element_to_List(Linked_List &linked_list, int element, int order)
{
int counter = 0;
Node_Address insertor_node_address,position_node_address;
position_node_address = linked_list;
//order 变量就是我们说的i
//现在要找到第i-1个节点
//因此首先必须要满足第i-1个节点是存在
//然后我们把第i-1个节点的地址赋给position_node_address
//这里的条件意思是
//如果我们还没到链表末尾,且counter还没等于i-1的话
//当counter = i-1时,跳出循环,此时position_node_address即为第i-1个节点的地址
while(position_node_address && counter < order-1 )
{
position_node_address = position_node_address->next_node_address;
counter ++;
}
//如果不存在第i-1个节点(链表过短,也就是order-1 > 此时的counter),或者输入不合法(i <1,即i-1<0)
if(!position_node_address || counter > order-1)
{
cout<<"invalid insertion!"<<endl;
return false;
}
insertor_node_address = new Node_Address;
insertor_node_address->data = element;
insertor_node_address->next_node_address = position_node_address->next_node_address;
position_node_address->next_node_address = insertor_node_address;
return true;
}
2.6 删除
删除对于链表是非常简单的事情
譬如我们想要删除第i个节点
步骤:
- 找到第i-1个节点(存在性)
- 利用第i-1个节点找到第i个节点;
- 将第i-1个节点的指针域指向第i个节点的下一个节点
- 释放第i个节点的空间,让它永远消失
bool Delete_an_Element_of_List(Linked_List &linked_list, int order)
{
Node_Address position_node_address, before_position_node_address;
int counter = 0;
before_position_node_address = linked_list;
//查找第i-1个节点,并借助第i-1节点的指针域证明第i个及诶单的存在性
//第i个节点存在,才能执行删除操作
while( (before_position_node_address->next_address) && counter < order-1)
{
before_position_node_address = before_position_node_address->next_node_address;
counter++;
}
if(!(before_positon_node_address->next_node_address) || counter>order-1)
{
cout<<"Invalid Deletion!"<<endl;
return false;
}
//删除操作
position_node_address = before_position_node_adress->next_node_address;
before_position_node_address->next_node_address = position_node_address->next_node_address;
//释放空间
delete position_node_address;
return true;
}