线性表的链式存储结构——单链表
一、线性表链式存储结构定义
基本概念:
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素
,这组线性单元可以是连续的,也可以是不连续的。
线性表的顺序存储结构的特点是逻辑关系上相邻的两个元素在物理位置上也相邻
,因此可以随机存取
表中任意元素。然而,对于顺序表进行插入、删除操作时需要通过移动数据元素来实现,影响了运行效率。而线性表的链式存储结构不需要用地址连续的存储单元来实现,因此它不要求逻辑上相邻的两个数据元素位置上也相邻,它是通过“链”建立起数据元素之间的逻辑关系,因此对线性表的插入、删除运算不需要移动数据元素。
链表可以分为单链表、循环单链表、双向链表。
二、单链表
1、单链表的节点
为建立起数据元素之间的线性关系,对每个数据元素ai
,除了存放自身信息之外,还需要存放其直接后继a(i+1)
所在的存储单元的地址,这两部分信息构成一个结点。存放数据元素的域成为数据域data
,存放其直接后继地址的域成为指针域next
。如下图:
n个节点链结成一个链表,即为线性表的链式存储结构,因为此链表的每个节点中只包含一个指针域,所以称为单链表。
单链表中每个结点的存储地址是存放其前驱结点的指针域中的,而第一个结点无前驱,因而应设一个头指针指向第一个节点。同时,由于表中最后一个节点没有直接后继,则指定线性表的最后一个节点为”空“(NULL)。
通常在线性链表的第一个结点之前附设一个头结点。头结点的数据域可以不用存放任何数据,也可以存放链表的结点个数。如上图c、d所示。
2、单链表的存储结构
/*线性表的单链表的存储结构*/
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
假设p
是指向线性表第i个元素的指针,则该结点ai
的数据域我们可以用p->data
来表示,p->data
是一个数据元素,结点ai
的指针域可以用p->next
来表示,p->next
是一个指针。而p->next
指向第i+1
个元素,即a(i+1)
。若p->next=ai
,则p->next->data=a(i+1)
。如下图所示:
3、单链表的基本运算
(1)单链表的创建
/*创建结点*/
Node *CreateNode(int data) //为方便,假设数据类型为int型,由节点数据类型决定。
{
Node* NewNode = (Node*)malloc(sizeof(Node)); // 申请分配结点存储空间。
assert(NewNode); // 检测存储空间是否申请成功。
NewNode->data = data; // 将数据存储到结点的数据域。
NewNode->next = NULL;// 指针域指为空。
return NewNode;
}
- 在单链表的头部插入结点建立单链表(逆序创建单链表)
头插法建立单链表简单,但读入数据元素的顺序与生成链表中的数据元素顺序是相反的。
/*头插法创建单链表*/
void InsertNodeByHead(Node*HeadNode, int data)
{
Node* pNode = CreateNode(data); //创建结点
pNode->next = HeadNode->next;
HeadNode->next = pNode ;
}
- 在单链表的尾部插入结点建立单链表
/*尾插法创建单链表*/
void InsertNodeByTail(Node* HeadNode, int data)
{
// 判断链表是否有结点。
assert(HeadNode != NULL);
if (headNode->next == NULL) {
//如果没有结点创建结点。
InsertNodeByHead(HeadNode, value);
return;
}
// 链表中有结点的情况。
// 找链表中的最后一个结点。
Node *cur;
for (cur = list->first; cur->next != NULL; cur = cur->next) {
}
// cur 就是最后一个结点。
Node *node = CreateNode(data);
cur->next = node;
}
(2)单链表的插入操作
插入操作主要指的是在线性表中的任意位置插入新元素。
算法思路:
a. 声明一个结点p
指向链表的第一个结点,初始化j从1开始;
b. 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
c. 若到链表末尾p为空,则说明插入位置不存在;否则查找成功,在系统中生成一个空结点s;
d. 将数据元素e赋值给s->data
;
e. 单链表的插入语句:s->next = p->next;p->next = s;
f. 返回成功。
/*在单链表任意位置插入数据元素*/
// HeadNode为要插入数据的链表,i为插入位置(i>0&&i<ListLength(HeadNode)),value为插入的数据。
bool InsertNode(Node* HeadNode, int i,int value){
// 遍历链表。
int j = 1;
Node* p = HeadNode;
while (p&&j < i){
p = p->next;
++j;
}
// 判断插入位置是否正确。
if (!p&&j>i){
return false;
}
// 给s申请存储空间,用于存储要插入的数据。
Node* s = (Node*)malloc(sizeof(Node));
s->data = value;
s->next = p->next;
p->next = s;
return true;
}
(3)单链表的删除操作
- 头删
头删的关键在于释放被删掉的结点所在的空间,另外需要注意进行判断进行删除操作的链表是否存在,该链表是否存在元素。
/*头删*/
// 头删,顾名思义就是从首元素进行删除
void DeleteNodeByHead(Node* HeadNode)
{
assert(HeadNode != NULL); // 判断链表是否存在。
assert(HeadNode->next != NULL); // 判断链表是否存在数据。
Node* p = HeadNode->next;
HeadNode->next = HeadNode->next->next;
free(p); // 释放p所在的空间。
}
- 尾删
在尾删操作中,我们需要遍历链表到最后一个元素的前一个元素进行操作,同样我们还需要将最后一个元素所在的空间释放。除此之外,当链表只有一个元素时,我们可以通过头删法删除这个元素。
/*尾删*/
// 尾删是从链表的尾部进行删除操作
void DeleteNodeByTail(Node* HeadNode)
{
assert(HeadNode != NULL);// 检查链表是否存在。
assert(HeadNode->next != NULL ); // 检查链表是否为空链表。
//当链表只有一个节点时,利用头删法删掉元素。
if(HeadNode->next->next == NULL){
DeleteNodeByHead(HeadNode);
return;
}
// tail指向表尾的前驱
Node* tail = HeadNode->next;
while(tail->next->next != NULL){
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
- 删除单链表中第i个位置的元素
算法思路:
a.判断链表是否存在;
b. 初始化j从1开始,并声明一个结点p指向链表的第一个节点;
c. 当j<i时,遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
d. 若找到链表末尾为空则,说明第i个元素不存在;
e. 否则查找成功,将p->next赋给q;
f. 删除:p->next=q->next;
g. 释放q结点;
h. 返回成功。
/*单链表的任意位置删除数据*/
bool DeleteNode(Node* HeadNode, int i)
{
assert(HeadNode != NULL); // 断言。
int j = 1;
Node* p = HeadNode;
// 遍历
while (p->next && j<i){
p = p->next;
j++;
}
if (!(p->next) || j>i){
return false;
}
// 删除操作
Node *q = p->next;
p->next = q->next;
free(q); // 释放空间。
return true;
}
(4)单链表的长度求取
/*链表的长度*/
int ListLength(Node* HeadNode)
{
int len = 0;
while(HeadNode->next != NULL){
HeadNode = HeadNode->next;
len++;
}
return len;
}
(5)单链表的读取操作
/*按值查找*/
int FindNode(Node *L, int value) // value为被查找的值
{
Node *temp = L;
int pos = 0;
int i = 1;
while (temp->next)
{
temp = temp->next;
if (value == temp->data)
{
pos = i;
printf("The %d position in the list is %d\n", elem, pos);
return pos; //返回elem元素在顺序表中的位置
}
i++;
}
printf("Serch error!\n");
}
(6)求取单链表的长度
/*单链表的长度*/
int ListLength(Node* HeadNode){
int len = 0;
while (HeadNode->next !=NULL){
HeadNode = HeadNode->next;
len++;
}
return len;
}
(7)打印单链表
/*打印单链表*/
// 遍历单链表并输出每一个结点的值。
void PrintList(struct Node* Head){
struct Node* head = Head->next;
while (head){
printf("%d", head->data);
head = head->next;
}
}
以上就是单链表的一些基本操作,还有另外一些操作,例如单链表的销毁,按结点查找,就不一一进行描述。由于本人水平有限,所以以上文章可能会有一些问题,欢迎大家点出来,谢谢。