目录
前言:暑假复习之链表的简单理解
链表是一种物理存储单元上非连续,非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的节点(链表中的每一个元素成为结点)组成,结点可以在运行时动态生成。
链表分为单链表,双向链表。下面介绍单链表:
一·单链表的设计思路
单链表是链表的一种,有多个节点组成,每一个结点都由指针域和数据域组成,数据域用来存储数据,指针域用来存储下一个节点的地址,从而指向下一个节点,链表的头节点的数据域不存储数据,指针域指向第一个真正存储数据的节点
typedef int ElemType;
typedef struct ListNode
{
ElemType data;//数据元素
struct ListNode* next;//指针域
}ListNode,*LinkList;
逻辑图示:
物理图示:
二·单链表的具体代码详解
1.初始化单链表
进行初始化操作时,将结构体中的成员变量赋一个初始值,在此时由于单链表会有多个节点的创建所以我们将创建节点和初始化节点分开写成两个函数,避免代码的冗余性。
//购买一个节点申请一个空间
ListNode* BuyNode()
{
ListNode* s = (ListNode*)malloc(sizeof(ListNode));
if (NULL == s)exit(1);
memset(s, 0, sizeof(ListNode));//如果申请失败的话,将申请的空间置为0
return s;
}
//清空所指元素的空间
void FreeNode(ListNode* p)
{
free(p);
}
//初始化单链表(初始化头节点)
ListNode* InitList()
{
ListNode* s = BuyNode();
s->next = NULL;
return s;
}
2.打印单链表
当p指针也就是head->next不为空时,循环打印链表的每一个元素,知道p指针指向为空时,跳出循环
//打印单链表
void PrintList(LinkList head)
{
assert(head != NULL);
ListNode* p = head->next;
while (p != NULL)
{
printf("%3d", p->data);
p = p->next;
}
printf("\n");
}
3.查询单链表
(1)按值查询
当p指针不为空,且指向的节点数据域不为要查询的值时进入循环,为查询的值时返回这个值
//查询单链表
ListNode* FindValue(LinkList head, ElemType val)
{
assert(head != NULL);
ListNode* p = head->next;
while (p != NULL&& p->data != val)
{
p = p->next;
}
return p;
}
(2)按位置查询
因为第一个节点为头节点没有数据域,所以int i=1;是从有数据的第一个节点开始的
//按位置查询
ListNode* FindPos(LinkList head, int pos)
{
assert(head != NULL);
int i = 1;
ListNode* p = head->next;
if (i < 1)
{
return NULL;
}
while (p != NULL&&i<pos)
{
p = p->next;
i++;
}
return p;
}
4.插入单链表
插入操作分为头插,尾插,其余位置插入。
(1)头插:在单链表的头部插入一个新节点,这个用指针比较方便,简单
//头插函数(头节点之后插入)
void Push_Front(LinkList head, ElemType val)
{
assert(head != NULL);
ListNode* s = BuyNode();
s->data = val;
s->next = head->next;
head->next = s;
//Insert_Next(head,head,val);
}
(2)尾插:在单链表的末尾插入,直接插入就行,改动最后一个节点的指针域即可
//尾插函数
void Push_Back(LinkList head, ElemType val)
{
assert(head != NULL);
ListNode* s = BuyNode();
ListNode* p = head;
while (p->next!= NULL)
{
p = p->next;
}
s->data = val;
s->next = NULL;
p->next = s;
}
(3)其余位置插入:先找到要将新节点插入到那个位置,找到位置改变节点指针域的指向,让他指向新的节点。
//在指定位置插入函数
bool InsertPos(LinkList head, int pos, ElemType val)
{
assert(head != NULL);
if (pos < 1)
{
return false;
}
ListNode* p = head;
int i = 1;
while (p->next != NULL && i < pos)
{
p = p->next;
i++;
}
ListNode* s = BuyNode();
s->data = val;
s->next = p->next;
p->next = s;
}
5.删除单链表的节点
删除操作有头删,尾删,其余位置删除
(1)其余位置删除:也是改变指针域的指向将其删除
//删除ptr指向的节点(删除后续节点)
bool Erase_Next(LinkList head, ListNode* ptr)
{
assert(head != NULL);
if (NULL == ptr || NULL == ptr->next)return false;
ListNode* q = ptr->next;
ptr->next = q->next;
FreeNode(q);
q = NULL;
return true;
}
(2)头删:删除头节点后的含有数据的节点
//头删
void Pop_Front(LinkList head)
{
assert(head != NULL);
Erase_Next(head, head);
}
(3)尾删:直接改变要删除节点的前驱节点的指针域
//尾删
void Pop_Back(LinkList head)
{
assert(head != NULL);
ListNode* pre = head;
ListNode* s = head->next;
while (s->next != NULL)
{
pre = s;
s = s->next;
}
Erase_Next(head, pre);
}
6.判空与判满
(1)判空:判断当前单链表是否为空链表
//判空函数
bool IsEmpty(LinkList head)
{
assert(head != NULL);
return head->next == NULL;
}
(2)判满:判断当前单链表是否满了
//判满函数
bool IsFull(LinkList head)
{
assert(head != NULL);
return head->next != NULL;
}
7.清空与销毁
(1)清空:判断链表为是否为空,若不为空直接利用头删函数将链表中的节点一个一个删掉
//清除函数
void ClearList(LinkList head)
{
assert(head != NULL);
while (!IsEmpty(head))
{
Pop_Front(head);
}
}
(2)销毁:利用清空函数销毁单链表
//摧毁函数
void DestroyList(LinkList head)
{
assert(head != NULL);
ClearList(head);
FreeNode(head);
}
三·单链表的总结
(1)动态链表,在堆上分配空间
(2)插入和删除操作时间复杂度低
(3)查询操作时间复杂度高,随机存取复杂