数据结构--单链表简单代码实现(总结)

Posted 庸人冲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构--单链表简单代码实现(总结)相关的知识,希望对你有一定的参考价值。

单链表

在这里插入图片描述

单链表的特点

单链表也是线性表的一种数据结构,所以它的也具备线性表的一些共同特点,即也是由零或多个数据类型相同的元素组成的有限序列。并且链表中除头结点(如果存在,没有则是第一个结点)外都有唯一的后继元素。而除最后一个节点外,每一个结点都有唯一一个前驱元素

它与顺序表的最大的不同点在于,顺序表的顺序是在逻辑和物理上共同存在的,而单链表的顺序则只存在逻辑上的顺序,物理上的存储是离散的。因此两种数据结构存在不同的优点和缺点:

顺序表

  1. 优点:查找元素的时间复杂度为O(1)即任何合法位置的元素都只需要执行一次操作即可查找到该元素。

  2. 缺点 :删除和插入操作需要移动大量元素,并且每删除一个或插入一个元素都要进行重复操作,每次操作的时间复杂度度都是O(n)。

单链表

  1. 优点:删除和插入操作时不需要移动大量元素,只需要将指针指向指定位置O(n),剩下的操作都是常数阶O(1)的操作。
  2. 缺点:每次查找或定位时时间复杂度都是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 ,返回函数的操作状态。

参数列表:

  1. LinkList* L, 需要被初始化的链表,接收的是头指针(即链表)的地址,形参本质是一个二级指针。
Status InitList(LinkList *L)
{
	*L = (LinkList)malloc(sizeof(Node));    // 创建头结点,并使头指针指向该节点
	if (!(*L))                              
		return ERROR;                      // 如果内存分配失败返回ERROR

	(*L)->next = NULL;                      // 头结点的指针域赋值为空
	return OK;           
}

插入结点

功能:向链表指定位置前插入新结点。

返回值:status ,返回函数的操作状态,插入成功成功返回OK,失败返回ERROR

参数列表:

  1. LinkList* L, 需要被初始化的链表。
  2. int n,指定插入的位置。
  3. 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

参数列表:

  1. 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,返回链表的长度。

参数列表:

  1. 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,返回操作结果的状态。

参数列表:

  1. 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

参数列表:

  1. LinkList L,需要判断的链表。
// 查看链表是否为空
Status ListIsEmpty(LinkList L)
{
	if (L->next)         // 头结点的指针域不为NULL,则进入该语句 
	{
		return FALSE;   // 返回FALSE
	}
	else {
		return TRUE;     // 否则返回TRUE
	}
}

获取指定位置的结点元素

功能:将指定位置结点的数据元素返回。

返回值:Status,该返回值只是返回操作状态,成功返回OK,失败返回ERROR

参数列表:

  1. LinkList L,被查找的链表。
  2. int n, 查找的位置。
  3. 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,返回指针元素结点的位置。

参数列表:

  1. LinkList L,被查找的链表。
  2. 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

参数列表:

  1. LinkList* L,被删除结点的链表。
  2. int n,被删除结点的位置。
  3. 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

参数列表:

  1. LinkList* L,被删除结点的链表。
  2. int n,被删除结点的位置。
  3. 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

参数列表:

  1. LinkList* L,创建的链表。
  2. 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

参数列表:

  1. LinkList* L,创建的链表。
  2. 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以上是关于数据结构--单链表简单代码实现(总结)的主要内容,如果未能解决你的问题,请参考以下文章

java数据结构:单链表常见操作代码实现

栈的简单实现-单链表实现

C语言教程双向链表学习总结和C语言代码实现!值得学习~

数据结构 单链表的简单理解和基本操作

单链表基本操作

Java实现单链表(步骤详解+源码)