Singly-Linked-List 单链表
趁着状态还不错,而且昨天刚刚发了线性表,现在赶紧来一发简单的单链表
链表从广义上来讲,属于采用指针动态化存储的线性表,在每次添加节点的时候,它向内存申请一 块 存储空间,在这一块存储空间内用于存储指针域和数值域。指针域 用于将链表的各个节点串联成一个链表,数值域 用于存储所需要存储的数据。
链表在逻辑上来说仍然属于线性表的一种,只是在存储形式上摆脱了是用数组存储而带来的存储上限的限制。理论上,链表的存储空间是无限的,但是由于电脑内存的限制,依旧会由于同时存在过多的链表节点而使得无法继续开辟空间来增加新的节点到链表中。
根据链表不同的连接方式,有以下几种常见的链表:
- 单向链表
- 双向链表
- 十字链表
在这里,Doge向大家简单的介绍一下单链表的相关基础知识和操作。
单链表 是指该链表的存储结构中所存在的指针域仅存在一个指向下一个节点的指针,通过该指针简单的将整个链表链接起来。
示意图如下:
对于单链表,依旧先给出它的结构声明:
struct ListNode; //定义链表节点结构
typedef struct ListNode *PtrToNode; //定义结点指针类型
typedef PtrToNode LinkedList; //定义链表头节点类型
typedef PtrToNode LLNode; //定义节点类型
typedef int ElementType; //定义数据类型
struct ListNode //节点结构
{
ElementType Element; //数值域
LLNode Next; //指针域
};
Doge 使用的单链表中采用了哑节点(DummyHead),即在链表的头部加入一个额外的节点来方便之后对于整个链表的数据操作等:
LinkedList Initial( void ) //初始化
{
LinkedList L;
L = ( LinkedList ) malloc ( sizeof( ListNode ) ); //分配头节点空间
L->Next = NULL;
return L;
}
对于链表,最基础的操作之一也是 插入(Insert) 操作,由于链表中不存在数据位置一说,因此 Doge 在这里只说一说简单插入而不使用定点插入,有兴趣的读者可以自行实现。对于简单插入,我们将数据直接链接到表头的后面来实现:
LinkedList Insert( LinkedList L, ElementType X ) //插入
{
LLNode P;
P = ( LLNode ) malloc ( sizeof( ListNode ) ); //分配插入节点的空间
P->Element = X; //节点数值域赋值
P->Next = L->Next; //将头节点的指针接到插入节点之后
L->Next = P; //将插入节点链接回到链表
return L;
}
在链表中,由于许多操作涉及到指针的移动,因此我们在查找数据的时候往往是去寻找它的前驱结点,来简化我们对于许多后续操作。同样,对于 查找(Find) 我们同样是借助 查找前驱(FindPrevious) 来实现:
LLNode FindPrevious( LinkedList L, ElementType X ) //查找前驱
{
LLNode P = L; //游标初始化
while( P->Next != NULL && P->Next->Element != X ) //遍历查找指定元素
P = P->Next;
if( P->Next == NULL ) //未找到指定元素,返回NULL
return NULL;
else //找到则返回前驱
return P;
}
LLNode Find( LinkedList L, ElementType X ) //查找
{
LLNode P = FindPrevious( L, X ); //使用查找前驱函数
if( P == NULL ) //未找到
{
cout << X << " is not found in the LinkedList!\n";
return NULL;
}
else //找到则返回指定元素节点
return P->Next;
}
删除指定元素,同样调用查找前驱函数,如果存在返回值则删除节点并且释放存储空间:
LinkedList Delete( LinkedList L, ElementType X ) //删除
{
LLNode P = FindPrevious( L, X ); //使用查找前驱函数
if( P == NULL ) //返回值为NULL
{
cout << X << " is not found in the LinkedList!\n";
return L;
}
else //删除节点并释放存储空间
{
LLNode tmp = P->Next;
P->Next = tmp->Next;
free( tmp );
return L;
}
}
我们在是用链表的时候有时候可以将原来存在的链表清空并重新使用这个表的时候就需要用到清空函数,同样,在删除链表的时候我们也需要先把链表清空:
LinkedList MakeEmpty( LinkedList L ) //清空链表
{
LLNode tmp = L->Next; //游标指向实际存储表的头
while( tmp != NULL ) //遍历清空链表
{
LLNode tmpp = tmp->Next;
free( tmp ); //释放存储空间
tmp = tmpp;
}
return L;
}
void Delete( LinkedList L ) //删除表
{
L = MakeEmpty( L ); //清空表
free(L); //删除表头
}
下面附上整合之后的C++代码:(未做实际程序测试,仅提供数据结构模板)
#include <cstdlib>
#include <iostream>
using namespace std;
struct ListNode; //定义链表节点结构
typedef struct ListNode *PtrToNode; //定义结点指针类型
typedef PtrToNode LinkedList; //定义链表头节点类型
typedef PtrToNode LLNode; //定义节点类型
typedef int ElementType; //定义数据类型
struct ListNode //节点结构
{
ElementType Element; //数值域
LLNode Next; //指针域
};
LinkedList Initial( void );
int IsEmpty( LinkedList L );
LinkedList Insert( LinkedList L, ElementType X );
LLNode Find( LinkedList L, ElementType X );
LLNode FindPrevious( LinkedList L, ElementType X );
LinkedList Delete( LinkedList L, ElementType X );
LinkedList MakeEmpty( LinkedList L );
void Delete( LinkedList L );
/*****************************************/
int main()
{}
/*****************************************/
LinkedList Initial( void ) //初始化
{
LinkedList L;
L = ( LinkedList ) malloc ( sizeof( ListNode ) ); //分配头节点空间
L->Next = NULL;
return L;
}
int IsEmpty( LinkedList L )
{
return ( L->Next == NULL );
}
LinkedList Insert( LinkedList L, ElementType X ) //插入
{
LLNode P;
P = ( LLNode ) malloc ( sizeof( ListNode ) ); //分配插入节点的空间
P->Element = X; //节点数值域赋值
P->Next = L->Next; //将头节点的指针接到插入节点之后
L->Next = P; //将插入节点链接回到链表
return L;
}
LLNode FindPrevious( LinkedList L, ElementType X ) //查找前驱
{
LLNode P = L; //游标初始化
while( P->Next != NULL && P->Next->Element != X ) //遍历查找指定元素
P = P->Next;
if( P->Next == NULL ) //未找到指定元素,返回NULL
return NULL;
else //找到则返回前驱
return P;
}
LLNode Find( LinkedList L, ElementType X ) //查找
{
LLNode P = FindPrevious( L, X ); //使用查找前驱函数
if( P == NULL ) //未找到
{
cout << X << " is not found in the LinkedList!\n";
return NULL;
}
else //找到则返回指定元素节点
return P->Next;
}
LinkedList Delete( LinkedList L, ElementType X ) //删除
{
LLNode P = FindPrevious( L, X ); //使用查找前驱函数
if( P == NULL ) //返回值为NULL
{
cout << X << " is not found in the LinkedList!\n";
return L;
}
else //删除节点并释放存储空间
{
LLNode tmp = P->Next;
P->Next = tmp->Next;
free( tmp );
return L;
}
}
LinkedList MakeEmpty( LinkedList L ) //清空链表
{
LLNode tmp = L->Next; //游标指向实际存储表的头
while( tmp != NULL ) //遍历清空链表
{
LLNode tmpp = tmp->Next;
free( tmp ); //释放存储空间
tmp = tmpp;
}
return L;
}
void Delete( LinkedList L ) //删除表
{
L = MakeEmpty( L ); //清空表
free(L); //删除表头
}