数据结构--单链表简单代码实现(总结)
Posted 庸人冲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构--单链表简单代码实现(总结)相关的知识,希望对你有一定的参考价值。
文章目录
单链表
单链表的特点
单链表也是线性表的一种数据结构,所以它的也具备线性表的一些共同特点,即也是由零或多个数据类型相同的元素组成的有限序列。并且链表中除头结点(如果存在,没有则是第一个结点)外都有唯一的后继元素。而除最后一个节点外,每一个结点都有唯一一个前驱元素。
它与顺序表的最大的不同点在于,顺序表的顺序是在逻辑和物理上共同存在的,而单链表的顺序则只存在逻辑上的顺序,物理上的存储是离散的。因此两种数据结构存在不同的优点和缺点:
顺序表
-
优点:查找元素的时间复杂度为O(1)即任何合法位置的元素都只需要执行一次操作即可查找到该元素。
-
缺点 :删除和插入操作需要移动大量元素,并且每删除一个或插入一个元素都要进行重复操作,每次操作的时间复杂度度都是O(n)。
单链表
- 优点:删除和插入操作时不需要移动大量元素,只需要将指针指向指定位置O(n),剩下的操作都是常数阶O(1)的操作。
- 缺点:每次查找或定位时时间复杂度都是O(n)。
单链表的存储结构
单链表中各节点关系是逻辑上的,它们直接的物理存储位置是随机的,可能连续也可能不连续。因此在存储结构上被分为数据域和指针域。
数据域:用来存储数据元素信息的内存空间。
指针域:用来存储后继元素的地址的内存空间。
这两部分组合起来称作结点。
在链表中,必须有头指针来指向第一个结点的低地址,头指针指向的就是链表的起点位置,通常也用头指针的名字来表示整个链表。
为了操作的统一,通常设有一个头节点,该节点的指针域指向了第一个结点,数据域则不存储数据信息,有需要可以存储表长等附加信息,头节点不是必要的,但是头结点可以使得对于整个表的操作更加统一。
代码描述
定义和结点的存储结构
// 定义
#define OK 1 // 状态返回值OK
#define ERROR 0 // 状态返回值ERROR
#define TRUE 1 // 状态返回值TRUE
#define FALSE 0 // 状态反回值FALSE
typedef int Status; // 函数的状态返回类型,返回值为上面四个
typedef int ElemType; // 元素数据类型,此处默认int
// 定义结点结构
typedef struct Node {
ElemType data; // 数据域
struct Node* next; // 指针域
}Node;
typedef Node* LinkList; // 链表
功能
初始化链表
功能:用于初始化链表。
操作结果:初始化成功返回OK
,失败返回ERROR
。
返回值:status
,返回函数的操作状态。
参数列表:
LinkList* L
, 需要被初始化的链表,接收的是头指针(即链表)的地址,形参本质是一个二级指针。
Status InitList(LinkList *L)
{
*L = (LinkList)malloc(sizeof(Node)); // 创建头结点,并使头指针指向该节点
if (!(*L))
return ERROR; // 如果内存分配失败返回ERROR
(*L)->next = NULL; // 头结点的指针域赋值为空
return OK;
}
插入结点
功能:向链表指定位置前插入新结点。
返回值:status
,返回函数的操作状态,插入成功成功返回OK
,失败返回ERROR
。
参数列表:
LinkList* L
, 需要被初始化的链表。int n
,指定插入的位置。ElemType e
,要插入的元素。
Status InsertNode(LinkList* L, int i, ElemType e)
{
// 创建结点指针p,s
LinkList p, s;
p = *L; // p指向头结点,从头结点开始向后遍历
int j = 1;
while ( p && j < i)
{
p = p->next; // p指向其指针域,直到指定位置前的结点结束
j++;
}
if (!p || j > i)
{
return ERROR; // 指定位置不正确,返回ERROR
}
// 创建新结点
s = (LinkList)malloc(sizeof(Node)); // s 指向结点
s->data = e; // 新结点数据域赋值
s->next = p->next; // 新结点指向指定位置处的结点
p->next = s; // 指针位置的前驱结点指向s, s此时成功插入指定位置
return OK;
}
输出链表元素
功能:打印输出链表的所有结点元素。
返回值:void
参数列表:
LinkList L
,需要打印的链表。
// 输出链表元素
void Printer(LinkList L)
{
LinkList p = L->next; // p指向链表第一个结点
while (p) // p指向的结点存在则进入循环
{
printf("%d ", p->data); // 打印p的数据域
p = p->next; // p指向后继结点
}
printf("\\n");
}
获取链表长度
功能:获取链表的长度。
返回值:int
,返回链表的长度。
参数列表:
LinkList L
,需要打印的链表。
// 获取链表长度
int ListLength(LinkList L)
{
LinkList p = L->next; // p指向链表第一个元素
int count = 0; // 计数器遍历
while (p) // p指向的结点存在则进入循环
{
p = p->next; // p指向后继结点
count++; // count+1
}
return count; // 返回count
}
清空链表
功能:将链表中所有元素结点释放,头结点除外。
返回值:Status
,返回操作结果的状态。
参数列表:
LinkList *L
,需要清空的链表。
Status ClearList(LinkList* L)
{
LinkList p, q;
p = (*L)->next; // p指向链表第一个结点
while (p) // p指向的结点存在则进入循环
{
q = p->next; // 将被清空结点的后继结点保存在q
free(p); // 释放p结点
p = q; // q赋值给p,继续下次循环
}
(*L)->next = NULL; // 所有结点释放完毕,最后将头结点的指针域指向NULL
return OK;
}
查看链表是否为空
功能:查看当前链表是否为空表。
返回值:Status
,空表返回TRUE
,非空表返回FALSE
。
参数列表:
LinkList L
,需要判断的链表。
// 查看链表是否为空
Status ListIsEmpty(LinkList L)
{
if (L->next) // 头结点的指针域不为NULL,则进入该语句
{
return FALSE; // 返回FALSE
}
else {
return TRUE; // 否则返回TRUE
}
}
获取指定位置的结点元素
功能:将指定位置结点的数据元素返回。
返回值:Status
,该返回值只是返回操作状态,成功返回OK
,失败返回ERROR
。
参数列表:
LinkList L
,被查找的链表。int n
, 查找的位置。ElemType* e
,接收查找到的元素,该操作真正的返回值。
Status GetElem(LinkList L, int n, ElemType* e)
{
LinkList p = L->next; // p 指向第一个结点
int j = 1; // j 从 1开始
while (p && j < n) // 当p指向的结点不为NULL 并且 j < n 时进入循环,说明要查找的位置还没到
{
p = p->next; // p指向后继结点
j++; // j+1;
}
if (!p || j > n) // 如果p为NULL, 或者j > n 说明 位置n上不存在结点
{
return ERROR; // 返回ERROR
}
*e = p->data; // 执行到这里,说明p已经是指定位置上的结点,将其指针域赋值给*e, 作为真正的返回值
return OK;
}
获取指定元素所在结点的位置
功能:将链表中指定元素第一个所在的位置返回。
返回值:int
,返回指针元素结点的位置。
参数列表:
LinkList L
,被查找的链表。ElemType e
,指定查找的元素。
int Locate(LinkList L, ElemType e)
{
int count = 0; // 计数器变量
LinkList p = L->next; // p指向第一个结点
while (p) // p不为NULL 说明该节点存在
{
count++; // count+1
if (p->data == e) // 如果 p的数据域 == 指定元素e
{
return count; // 返回计数器变量count
}
p = p->next; // 没有进入if,则使p指向后继结点,继续循环判断
}
return 0; // 代码执行到此处,说明要么该表为空表,要么e元素不存在表中,返回0
}
指定位置删除结点
功能:删除链表中指定位置上的结点,并返回被删除结点的元素。
返回值:status
,返回函数的操作状态,删除成功返回OK
,失败返回ERROR
。
参数列表:
LinkList* L
,被删除结点的链表。int n
,被删除结点的位置。ElemType* e
,接收被删除结点的元素,这是该操作正在的返回值。
// 指定位置删除结点
Status DeleteNodeInPos(LinkList* L, int n, ElemType* e)
{
LinkList p, q;
p = *L; // p 指向头结点
int j = 1; // j 从一开始
while ( p->next && j < n) // p的指针域不为NULL 并且, j < n 说明没有达到指定位置,则进入循环。
{
p = p->next; // p 指向后继结点。
j++; // j++;
}
if (!(p->next) || j > n) // p的指针域为空 或则 j > n 说明指定位置不存在结点。
{
return ERROR; // 返回ERROR
}
q = p->next; // 将被删除结点p->next 保存在q中,q变为被删除结点
*e = q->data; // 把被删除结点的数据传给*e
p->next = q->next; // 让删除结点的前驱结点指向其后继结点
free(q); // 释放q结点
return OK;
}
指定元素删除结点
功能:删除链表中第一个指定元素所在的结点。
返回值:status
,返回函数的操作状态,删除成功返回OK
,失败返回ERROR
。
参数列表:
LinkList* L
,被删除结点的链表。int n
,被删除结点的位置。ElemType* e
,接收被删除结点的元素,这是该操作正在的返回值。
Status DeleteNodeInElem(LinkList* L, ElemType e, int* posi)
{
LinkList p, q;
p = *L; // p指向头结点
int count = 0; // 计数器变量
while (p->next) // p->next不为NULL 则进入循环, 最终删除的是p->next结点
{
count++; // 进入循环说明存在一个结点
if (p->next->data == e) // 如果p->next结点的数据域 == e, 进入循环
{
q = p->next; // 将被删除结点p->next 保存在q中,q变为被删除结点
p->next = q->next; // 让删除结点的前驱结点指向其后继结点
free(q); // 释放q结点
*posi = count; // 将count的值保存在*posi中,作为正真的返回值,返回的是被删除结点的位置。
return OK;
}
p = p->next; // 如果没有进入if语句,则 p继续指向后继结点
}
*posi = 0; // 代码运行到这里要么没进入循环,说明是空表;要么遍历完毕也没找到匹配元素,将0存入*posi。
return ERROR; // 返回ERROR
}
整表创建(头插)
功能:创建一个结点个数为n
的链表,链表中的元素随机赋值,新结点将添加到表头位置。
返回值:void
参数列表:
LinkList* L
,创建的链表。int n
,链表的结点个数。
void CreatListInHead(LinkList* L, int n)
{
*L = (LinkList)malloc(sizeof(Node)); // 创建头结点
(*L)->next = NULL; // 初始化链表
int i = 0;
srand((unsigned)time(NULL)); // 初始化随机种子
LinkList s; // s用于指向新结点
for (i = 0; i < n; i++)
{
s = (LinkList)malloc(sizeof(Node)); // 生成新结点
s->data = rand() % 100 + 1; // 新结点数据域赋值
// 头插关键步骤
s->next = (*L)->next; // 新结点总是指向第一个结点(不存在则指向NULL)
(*L)->next = s; // 头结点再指向新结点,此时新结点就是第一个结点
}
}
整表创建(尾插)
功能:创建一个结点个数为n
的链表,链表中的元素随机赋值,新结点将添加到表尾位置。
返回值:void
参数列表:
LinkList* L
,创建的链表。int n
,链表的结点个数。
// 整表创建(尾插)
void CreatListInTail(LinkList* L, int n)
{
LinkList s, r; // s指向新结点,r指向尾结点
srand((unsigned)time(NULL)); // 初始化随机种子
*L = (LinkList)malloc(sizeof(Node)); // 创建头结点
(*L)->next = NULL; // 初始化列表
r = *L; // r用来指向尾结点,空表则指向头结点
int j = 0;
for (j = 0; j < n; j++) // 循环n次创建n个结点
{
s = (LinkList)malloc(sizeof(Node)); // 创建新结点
s->data = rand() % 100 + 1; // 新结点数据域赋值
// 尾插关键步骤
r->next = s; // r作为当前尾节点,其指针域总是指向新结点,s此时的位置在r后面
r = s; // 因为s在r的后面,那么就得使s称为尾节点,下次新建的结点就在s的后面
}
r->next = NULL; // 创建完毕,再把尾结点的指针域指向NULL
}
完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 定义
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
typedef int ElemType;
// 定义结点结构
typedef struct Node {
ElemType data; // 数据域
struct Node* next; // 指针域
}Node;
typedef Node* LinkList; // 链表
// 初始化链表
// 返回值:Status ,返回操作状态
// 参数列表: LinkList *L, 链表头指针
Status InitList(LinkList *L)
{
*L = (LinkList)malloc(sizeof(Node)); // 创建头结点,并使头指针指向该节点
if (!(*L)) // 内存分配失败
return ERROR;
(*L)->next = NULL; // 头结点的指针域赋值为空
return OK;
}
// 指定位置前插入结点
Status InsertNode(LinkList* L, int n, ElemType e)
{
// 创建结点指针p,s
LinkList p, s;
p = *L; // p指向头结点,从头结点开始向后遍历
int j = 1;
while ( p && j < n)
{
p = p->next; // p指向其指针域,直到指定位置前的结点结束
j++;
}
if (!p || j > n) // 指定位置不正确
{
return ERROR;
}
// 创建新结点
s = (LinkList)malloc(sizeof(Node));
s->data = e; // 新结点数据域赋值
s->next = p->next; // 新结点指向指定位置处的结点
p->next = s; // 指针位置结点前前驱结点, s 此时成功插入指定位置
return OK;
}
// 输出链表元素
void Printer(LinkList L)
{
LinkList p = L->next;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
printf("\\n");
}
// 获取链表长度
int ListLength(LinkList L)
{
LinkList p = L->next;
int count = 0;
while (p)
{
p = p->next;
count++;
}
return count;
}
// 清空链表
Status ClearList(LinkList* L)
{
LinkList p, q;
p = (*L)->next;
while (p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL; // 所有结点释放完毕,最后将头结点的指针域指向NULL
return OK;
}
// 查看链表是否为空
Status ListIsEmpty(LinkList L)
{
if (L->next)
{
return FALSE;
}
else {
return TRUE;
}
}
// 获取指定位置元素
Status GetElem(LinkList L, int n, ElemType* e)
{
LinkList p = L->next;
int j = 1;
while (p && j < n)
{
p = p->next;
j++;
}
if (!p || j > n)
{
return ERROR;
}
*e = p->data;
return OK;
}
// 获取指定元素所在结点的位置
int Locate(LinkList L, ElemType e)
{
int count = 0;
LinkList p = L->next;
while (p)
{
count++;
if (p->data == e)
{
return count;
}
p = p->next;
}
return 0;
}
// 指定位置删除结点
Status DeleteNodeInPos(LinkList* L, int n, ElemType* e)
{
LinkList p, q; // 创建2个结点指针p q
p = *L; // p 指向头结点,指向头结点是因为,加上删除的结点是An, 那么则需要将An-1 与 An+1联系起来,因此p最终指向的位置应该是An-1,才能对后面两个结点进行操作
int j = 1; // j = 1,如果j = 0 那么 j~n之间就要循环n次,则p最终会指向An,而我们需要指向An-1
while ( p->next && j < n) //p->next 作为判断条件 如果p->next不为NULL 说
{
p = p->next;
j++;
}
if (!(p->next) || j > n)
{
return ERROR;
}
q = p->next; // q指向被删除结点An
*e = q->data; // 把被删除结点的数据传给*e
p->next = q->next; // 让An-1 指向 An+1
free(q); // 释放An
return OK;
}
// 指定元素删除结点 删除第一个出现在链表中的指定元素所在结点
Status DeleteNodeInElem(LinkList* L, ElemType e, int* posi)
{
LinkList p, q;
p = *L; // 同样从头结点开始,因为要对被删除结点An进行操作
int count = 0; // 计数器
while (p->next) // p->next最终指向An
{
count++; // 进入循环说明存在一个结点
if (p->next->data == e) // An的数据域如果 == e
{
q = p->next; // 让q 指向 An
p->next = q->next; // 让An-1 指向 An+1
free以上是关于数据结构--单链表简单代码实现(总结)的主要内容,如果未能解决你的问题,请参考以下文章