一、单链表相关知识点介绍:
1. 结点:结点就是单链表中研究的数据元素,结点中存储数据的部分称为数据域,存储直接后继地址的部分称为指针域。
2. 头结点:引入头结点的目的是,将链表首元结点的插入和删除操作与其他结点的插入和删除操作统一起来。(即头指针地址不在发生变化)
3. 头指针:头指针始终指向链表第一个元素,当有头结点时头结点就是链表第一个元素。头指针具有标识左右,故头指针命名为链表的名字。头指针是一定存在的。
4. 带头结点的单链表的结点示意图:
5、带头结点的单链表结点定义:
typedef struct Node
{
int data;
struct Node* next;
}Node,*List;
二、单链表的操作集合
- 头文件 list.h
#pragma once
//带头结点的单链表,单链表的尾节点的next为NULL
//链表中不使用下标
typedef struct Node
{
int data;//数据
struct Node *next;//后继指针
}Node,*List;//List=Node*
//初始化
void InitList(List plist);
//头插,将数据插入到链表的第一个位置
bool Insert_head(List plist,int val);
//尾插,将数据插入到链表的末尾
bool Insert_tail(List plist,int val);
//将数据val,插入到plist中的pos位置
bool Insert_pos(List plist,int val,int pos);//不常用
//在plist中查找关键字key,成功返回节点地址,失败返回NULL
Node *Search(List plist,int key);
//删除plist中的第一个key
bool Delete(List plist,int key);
//清空数据
void Clear(List plist);
//销毁链表
void Destroy(List plist);
2.初始化的实现
//初始化
void InitList(List plist)
{
assert(plist != NULL);
plist->next = NULL;
}
3.头插法的实现元素插入
//头插,将数据插入到链表的第一个位置
bool Insert_head(List plist,int val)
{
Node *p = (Node *)malloc(sizeof(Node));//动态开辟内存空间
assert(p != NULL);//可有可无
p->data = val;
p->next = plist->next;
plist->next = p;
/*
Node newnode;//1 //错误一局部变量,生命周期限制
newnode.data = val;//2
plist->next = &newnode;//3 //错误2,插入顺序不对
newnode.next = plist->next;//4
*/
return true;
}
4.尾插法的实现元素插入
//尾插,将数据插入到链表的末尾
bool Insert_tail(List plist,int val)
{
Node *p;
Node *q = (Node *)malloc(sizeof(Node));
q->data = val;
for(p=plist; p->next!=NULL; p=p->next) ; //找尾巴
//q插入在p的后面
q->next = p->next;//q->next = NULL;
p->next = q;
return true;
}
5.在链表中查找关键字key
//在plist中查找关键字key,成功返回节点地址,失败返回NULL
Node *Search(List plist,int key)
{
for(Node *p=plist->next;p!=NULL;p=p->next)
{
if(p->data == key)
{
return p;
}
}
return NULL;
}
6.查找key 的前驱结点
//查找key的前驱节点
static Node *SearchPri(List plist,int key)
{
for(Node *p=plist;p->next!=NULL;p=p->next)
{
if(p->next->data == key)
{
return p;
}
}
return NULL;
}
7.删除链表中的第一个key
//删除plist中的第一个key
bool Delete(List plist,int key)
{
Node *p = SearchPri(plist,key);
if(p == NULL)
{
return false;
}
Node *q = p->next;
p->next = q->next;//p->next = p->next->next;
free(q);
return true;
}
8.清空数据
//清空数据,删除所有的数据节点
void Clear(List plist)
{
Destroy(plist);
}
9.销毁数据
//销毁链表
void Destroy(List plist)
{
Node *p;
while(plist->next != NULL)
{
p = plist->next;
plist->next = p->next;
free(p);
}
/*
Node *p = plist->next;
Node *q;
plist->next = NULL;
while(p != NULL)
{
q = p->next;
free(p);
p = q;
}
*/
}
10.显示数据
//显示数据
void Show(List plist)
{
for(Node *p=plist->next;p!=NULL;p=p->next)
{
printf("%d ",p->data);
}
printf("\n");
}
11.链表逆置
//链表逆置
void Reverse(List plist)
{
//if(plist==NULL || plist->next->next==NULL || plist->next==NULL)//bug
if(plist==NULL || plist->next==NULL || plist->next->next==NULL)
{
return ;
}
Node *p = plist->next;
Node *q;
plist->next = NULL;
while(p != NULL)
{
q = p->next;
p->next = plist->next;//头插的算法
plist->next = p;
p = q;
}
/*//第二种方法
Node *p = plist->next;
Node *q = p->next;
Node *s;
p->next = NULL;
while(q != NULL)
{
s = q->next;
q->next = p;
p = q;
q = s;
}
plist->next = p;
*/
}
三、顺序表与单链表的比较
顺序表是用一段地址连续的存储单元来存放数据元素,适合查找,不适合频繁插入和删除(每一次操作都是O(n));
单链表是用不连续的存储单元,其数据元素之间是用指针来连接,每一次的查找都必须通过头指针来遍历,因此其不适合频繁的查找。但是对于插入和删除操作,它不需要移动其它元素,算法时间复杂度为O(1),因此特别适合频繁的插入和删除操作。