顺序表和链表-10000字详解

文章讲解了顺序表和链表的相关知识,以及动态顺序表和链表的详细代码的教程!!!大家彼此加油!!!😜😜🐱‍👤😜🐱‍🏍😃😊😂😁💕😘👌😍🤦‍♀️🤦‍♀️🤦‍♂️🤷‍♂️😘👌😍❤❤🌹💋

lesson2 - 顺序表和链表

    1. 线性表
      a.顺序表
      b.链表
    1. 顺序表
      a. 动态顺序表
      b. 动态顺序表的基础功能实现代码
      c. 顺序表项目文件
    1. 单向链表
      a. 链表的概念及结构
      b. 链表的分类
      c. 动态顺序表的基础功能实现代码
      d. 单向链表项目文件
    1. 双向链表
      a.双向链表
      b.双链表的基础功能实现代码
      c.双向链表项目文件
    1. 顺序表与链表的创建
    1. 顺序表链表优缺点
      a.存储空间上的不同
      b.随机访问的复杂度不同
      c.任意位置的插入或者删除的复杂度不同
      d.扩容的不同
      e.缓存利用率不同
      f.适用的场景
      总结

1.线性表

线性表时n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就是说是一条连续的线。但是在物理结构上并不一定连续的,线性表在物理上存储时,通常以数组和链式结构的形式进行存储。
在这里插入图片描述

内容可能较抽象,可以借助之后的内容加以理解,所以这里不会也没关系,只需要知道顺序表和链表都归属于线性表这一类就可以!!!

2.顺序表

a. 动态顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
在这里插入图片描述

顺序表一般可以分为:

  • 静态顺序表:使用定长数组存储元素
    在这里插入图片描述

  • 动态顺序表
    在这里插入图片描述

b.动态顺序表的基础功能实现代码

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N过大,造成空间资源的浪费,过小导致空间资源不够。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。(学会了动态,简单的静态也可以自己实现咯)

  • 创建动态顺序表
//顺序表的动态存储//
typedef int SLDataType;
typedef struct SeqList
{
	int* a;  //指向动态开辟的数组
	size_t size;  //记录有效数据个数
	size_t capicity; //空间容量大小
}SL;
  • 顺序表初始化(不同的线性表的初始化是不同的)
//初始化顺序表
void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->size = 0;//记录有效数据个数
	ps->capacity = 0;//空间容量大小
	ps->a = NULL;//指向动态开辟的数组
}
  • 顺序表扩容
//增容
void SLCheckCapacity(SeqList* ps)
{
	assert(ps);//断言
	
	int newcapacity = (ps->capacity == 0 ? 4 : 2 * ps->capacity);//判断空间是否从未开辟空间过
	int* tmp = (int*)realloc(ps->a, newcapacity * sizeof(int));//开辟空间
	if (tmp == NULL)
	{
		perror("realloc fail");
		exit(-1);
	}
	ps->capacity = newcapacity;//重新赋值空间容量大小
	ps->a = tmp;//开辟成功
}
  • 顺序表尾插
//尾插
void SeqListPushBack(SeqList* ps, SLDateType x)
{
    if (ps->size == ps->capacity)//判断空间是否满
	{
	    SLCheckCapacity(ps);
    }
	ps->a[ps->size] = x;
	ps->size++;
}
  • 尾删(在尾部删除一个数据)
//尾删
void SeqListPopBack(SeqList* ps)
{
	assert(ps);
	assert(ps->size>0);//判断 是否有 有效数据
	ps->size--;//有效数据减1
}
  • 首插
//首增加
void SeqListPushFront(SeqList* ps, SLDateType x)
{
    if (ps->size == ps->capacity)//判断空间是否满
	{
	    SLCheckCapacity(ps);
    }
	for (unsigned int i = ps->size; i > 0; i--)//挪动数据
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;//对首元素进行赋值
	ps->size++;//有效数据加1
}
  • 首删
//首减少
void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	assert(ps->size>0);//防止数组越界
	for (unsigned int i = 0; i < ps->size-1; i++)//挪动数据
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}
  • 顺序表查找
//顺序表查找
int SeqListFind(SeqList* ps, SLDateType x)
{
	assert(ps);
	for (unsigned int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;//找到返回下标
		}
	}
	return -1;//找不到返回-1
}
  • 顺序表在pos位置插入x(pos代表的是第几个元素)
// 顺序表在pos位置插入x
//pos代表的是第几个元素
void SeqListInsert(SeqList* ps, size_t pos, SLDateType x)
{
    assert(ps);
    assert(pos >= 1);//规定pos位置的取值范围
	assert(pos <= ps->size+1);//规定pos位置的取值范围,可以尾插和首插
	if (ps->size == ps->capacity)//判断空间是否满
	{
	    SLCheckCapacity(ps);
    }
	for (unsigned int i = ps->size; i > pos-1; i--)//挪动数据
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos-1] = x;
	ps->size++;
}
  • 顺序表删除pos位置的值(pos代表的是第几个元素)
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos)
{
	assert(ps);
	assert(pos >= 1);
	assert(pos < ps->size+1);
	for (unsigned int i = pos-1; i < ps->size-1; i++)//挪动数据
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}
  • 显示循序表
//显示顺序表
void SeqListPrint(SeqList* ps)
{
    assert(ps);
	for (unsigned int i = 0; i < ps->size; i++)//打印顺序表
	{
		printf("%d\n", ps->a[i]);
	}
}
  • 释放顺序表
//释放顺序表
void SeqListDestroy(SeqList* ps)
{
	assert(ps);
	ps->size = 0;
	ps->capacity = 0;
	if (ps->a != NULL)
	{
		free(ps->a);
	}
	ps->a = NULL;
}

c.顺序表项目文件

c.1 头文件SLT.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDateType;
typedef struct SeqList//顺序表
{
	SLDateType* a;
	size_t size;//有效数据个数
	size_t capacity;//空间容量大小
}SeqList;

//
void mean();
//初始化顺序表//
void SeqListInit(SeqList* ps);
//销毁顺序表//
void SeqListDestroy(SeqList* ps);

//增容
void SLCheckCapacity(SeqList* ps);
//显示顺序表
void SeqListPrint(SeqList* ps);

//尾插//
void SeqListPushBack(SeqList* ps, SLDateType x);
//首插//
void SeqListPushFront(SeqList* ps, SLDateType x);


//首删//
void SeqListPopFront(SeqList* ps);
//尾删//
void SeqListPopBack(SeqList* ps);


//顺序表查找//
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x//
void SeqListInsert(SeqList* ps, size_t pos, SLDateType x);
// 顺序表删除pos位置的值//
void SeqListErase(SeqList* ps, size_t pos);

c.2 函数C文件SLT.c

#include"SLT.h"
void mean()
{
	printf("************************************************\n");
	printf("****************1. 尾插*************************\n");
	printf("****************2. 尾删*************************\n");
	printf("****************3. 首增*************************\n");
	printf("****************4. 首删*************************\n");
	printf("****************5. 显示顺序表*******************\n");
	printf("****************6. 顺序表查找*******************\n");
	printf("****************7. 中间插入数据*****************\n");
	printf("****************8. 中间删除数据*****************\n");
	printf("****************0. 程序结束*********************\n");
	printf("************************************************\n");
}
//初始化顺序表
void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->size = 0;
	ps->capacity = 0;
	ps->a = NULL;
}

//释放顺序表
void SeqListDestroy(SeqList* ps)
{
	assert(ps);
	ps->size = 0;
	ps->capacity = 0;
	if (ps->a != NULL)
	{
		free(ps->a);
	}
	ps->a = NULL;
}
//增容
void SLCheckCapacity(SeqList* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)
	{
		int newcapacity = (ps->capacity == 0 ? 4 : 2 * ps->capacity);
		int* tmp = (int*)realloc(ps->a, newcapacity * sizeof(int));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->capacity = newcapacity;
		ps->a = tmp;
	}
}
//遍历打印顺序表
void SeqListPrint(SeqList* ps)
{
	for (unsigned int i = 0; i < ps->size; i++)
	{
		printf("%d\n", ps->a[i]);
	}
}

//尾插
void SeqListPushBack(SeqList* ps, SLDateType x)
{
	SeqListInsert(ps, ps->size+1, x);
}

//尾删
void SeqListPopBack(SeqList* ps)
{
	SeqListErase(ps, ps->size);
}

//首增加
void SeqListPushFront(SeqList* ps, SLDateType x)
{
	SeqListInsert(ps, 1, x);
}


//首减少
void SeqListPopFront(SeqList* ps)
{
	//assert(ps);
	SeqListErase(ps, 1);//
}

//顺序表查找
int SeqListFind(SeqList* ps, SLDateType x)
{
	assert(ps);
	for (unsigned int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;//找到返回下标
		}
	}
	return -1;//找不到返回-1
}

// 顺序表在pos位置插入x
//pos代表的是第几个元素
void SeqListInsert(SeqList* ps, size_t pos, SLDateType x)
{
	SLCheckCapacity(ps);
	assert(pos >= 1);
	assert(pos <= ps->size+1);
	for (unsigned int i = ps->size; i > pos-1; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos-1] = x;
	ps->size++;
}
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos)
{
	assert(ps);
	assert(pos >= 1);
	assert(pos < ps->size+1);
	for (unsigned int i = pos-1; i < ps->size-1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

c.3 功能实现C文件test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "SLT.h"

void test()
{
	int n = 0;
	int x = 0;
	int pos = 0;
	SeqList ps;
	SeqListInit(&ps);//初始化顺序表
	do
	{
		mean();//菜单
		printf("请选择功能>:");
		scanf("%d", &n);
		switch (n)
		{
		case 1://尾插
			printf("请输入要尾插的数据>:");
			scanf("%d", &x);
			SeqListPushBack(&ps, x);
			break;
		case 2://尾减
			SeqListPopBack(&ps);
			break;
		case 3://首增
			printf("请输入要增加的数据>:");
			scanf("%d", &x);
			SeqListPushFront(&ps, x);
			break;
		case 4://首减
			SeqListPopFront(&ps);
			break;
		case 5://显示顺序表
			SeqListPrint(&ps);
			break;
		case 6://顺序表查找
			printf("请输入要查找的数据>:");
			scanf("%d", &x);
			printf("您所查找的数据的下标是>:%d\n", SeqListFind(&ps, x));
			break;
		case 7://中间插入数据
			printf("请输入要插入的数据>:");
			scanf("%d", &x);
			printf("请输入要插入在第几位>:");
			scanf("%d", &pos);
			SeqListInsert(&ps, pos, x);
			break;
		case 8://中间删除数据
			printf("请输入要删除第几位的数据>:");
			scanf("%d", &pos);
			SeqListErase(&ps, pos);
			break;
		case 0://程序结束
			SeqListDestroy(&ps);
			break;
		}
	}while(n);
}

int main()
{
	test();
	return 0;
}

2.链表

a.链表的概念及结构

概念:链表是一个物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

创建链表(单向链表)

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

单向链表就好比火车,火车也并不是连续的空间,火车是由一个个车厢所构成的,之所以能从一个车厢进入另一个车厢是因为车厢间的车钩将一个个车厢连接起来。而单向链表中的typedef struct SListNode(一个链表单元) 就是一个个车厢,struct SListNode * next就是一个个车钩!!!

在这里插入图片描述

在这里插入图片描述

b.链表的分类

实际中的链表的结构非常多样,以下情况组合起来就有8中链表结构:

  • 1.单向或双向
    在这里插入图片描述

  • 2.带头或者不带头
    在这里插入图片描述

  • 3.循环或非循环
    在这里插入图片描述
    虽然有这么多的链表的结构,但是我们实际中最常用的还是两种结构:
    在这里插入图片描述

1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2.带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

c.单向链表的基础功能实现代码

  • 创建链表
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;
  • 创建独立单元/初始化独立单元
SLTNode* BuySLTNode(SLTDataType x)//初始化独立单元//开辟独立单元
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("maloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
  • 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)//尾插
{
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
  • 尾删
void SLTPopBack(SLTNode** pphead)//尾删
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else 
	{
		SLTNode* src = (*pphead)->next,*dec = *pphead;
		while (src->next)
		{
			src = src->next;
			dec = dec->next;
		}
		free(src);
		dec->next = NULL;
	}
}
  • 首插
void SLTPushFront(SLTNode** pphead, SLTDataType x)//首插
{
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
		*pphead = newnode;
	else
	{
		//找尾
		newnode->next = *pphead;
		*pphead = newnode;
	}	
}
  • 首删
void SLTPopFront(SLTNode** pphead)//首删
{
	assert(*pphead);
	SLTNode* next = (*pphead)->next;//取地址
	free(*pphead);//释放空间
	*pphead = next;//
}
  • 在pos位置之后插入一个新的单元链表
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);//创建一个新的单元链表
	SLTNode* p = pos->next;//取pos->next的地址
	pos->next = newnode;//
	newnode->next = p;//
	free(p);
	p = NULL;
}
  • 删除pos位置之前的元素
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SLTNode* pos)
{
	assert(pos);
	SLTNode* p=pos->next;//取要删除的地址
	pos->next = p->next;
	free(p);
	p = NULL;
}
  • 遍历打印链表
void SLTPrint(SLTNode* phead)//遍历打印链表元素
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}
  • 单向链表销毁
void SListDestroy(SLTNode* plist) {//销毁
	SLTNode* p = plist->next;
	while (p->next != NULL)
	{
		free(plist);
		plist = p;
		p = p->next;
	}
	free(plist);
	free(p);
	p = NULL;
	plist = NULL;
}

d.顺序表项目文件

d.1 头文件SList.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;
//独立单元:链表是由一个一个独立单元经过有机结合形成的
SLTNode* BuySLTNode(SLTDataType x);//初始化独立单元//开辟独立单元

void SLTPrint(SLTNode* phead);//遍历打印链表元素

void SLTPushBack(SLTNode** pphead, SLTDataType x);//尾插
void SLTPopBack(SLTNode** pphead);//尾删
void SLTPushFront(SLTNode** pphead, SLTDataType x);//首插
void SLTPopFront(SLTNode** pphead);//首删

SLTNode* SListFind(SLTNode* plist, SLTDataType x);//遍历查找


// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SLTNode* pos, SLTDataType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SLTNode* pos);


// 在pos之前插入x
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
// 删除pos位置
void SListErase(SLTNode** pphead, SLTNode* pos, SLTDataType x);


// 单链表的销毁
void SListDestroy(SLTNode* plist);

d.2 函数文件Slist.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

SLTNode* BuySLTNode(SLTDataType x)//初始化独立单元//开辟独立单元
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("maloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}


void SLTPrint(SLTNode* phead)//遍历打印链表元素
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void SLTPushBack(SLTNode** pphead, SLTDataType x)//尾插
{
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

void SLTPopBack(SLTNode** pphead)//尾删
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else 
	{
		SLTNode* src = (*pphead)->next,*dec = *pphead;
		while (src->next)
		{
			src = src->next;
			dec = dec->next;
		}
		free(src);
		dec->next = NULL;
	}
}

void SLTPushFront(SLTNode** pphead, SLTDataType x)//首插
{
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
		*pphead = newnode;
	else
	{
		//找尾
		newnode->next = *pphead;
		*pphead = newnode;
	}	
}

void SLTPopFront(SLTNode** pphead)//首删
{
	assert(*pphead);
	SLTNode* next = (*pphead)->next;//取地址
	free(*pphead);//释放空间
	*pphead = next;//
}
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);//创建一个新的单元链表
	SLTNode* p = pos->next;//取pos->next的地址
	pos->next = newnode;//
	newnode->next = p;//
	free(p);
	p = NULL;
}
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SLTNode* pos)
{
	assert(pos);
	SLTNode* p=pos->next;//取要删除的地址
	pos->next = p->next;
	free(p);
	p = NULL;
}
void SListDestroy(SLTNode* plist) {//销毁
	SLTNode* p = plist->next;
	while (p->next != NULL)
	{
		free(plist);
		plist = p;
		p = p->next;
	}
	free(plist);
	free(p);
	p = NULL;
	plist = NULL;
}

d.3 功能实现C文件test.c

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include "SList.h"

void TestSList1()
{
	int n = 0;
	int x = 0;
	int pos = 0;
	//SLTNode* Plist = CreateSList(10);
	SLTNode* Plist = NULL;
	do {
		printf("请选择相应的功能>:");
		scanf("%d", &n);
		switch (n)
		{
		case 1:
			printf("请输入你要写入的元素>:");
			scanf("%d", &x);
			SLTPushBack(&Plist, x);//尾插
			break;
		case 2:
			SLTPopBack(&Plist);//尾删
			break;
		case 3:
			printf("请输入你要写入的元素>:");
			scanf("%d", &x);
			SLTPushFront(&Plist, x);//首插
			break;
		case 4:
			SLTPopFront(&Plist);//首删
			break;
		
		case 5:
			SLTPrint(Plist);//遍历打印链表元素
			break;
		case 6://在pos位置后插入x
			printf("请输入要插入的x的值>:");
			scanf("%d", &x);
			printf("请输入要插入的位置>:");
			scanf("%d", &pos);
			SLTNode* p1 = Plist;
			for (int i = 0; i < pos-1; i++)
			{
				p1 = p1->next;
			}
			SListInsertAfter(p1, x);
			break;
		case 7://在pos位置后删除
			printf("请输入要删除的位置>:");
			scanf("%d", &pos);
			SLTNode* p2 = Plist;
			for (int i = 0; i < pos - 1; i++)
			{
				p2 = p2->next;
			}
			SListEraseAfter(p2);
			break;
		case 0://销毁
			SListDestroy(Plist);
			break;
		}
	} while (n);
}
int main()
{
	TestSList1();
	return 0;
}

3.双向链表

a.双向链表的概念及结构

双向链表的结点包括三个部分:前驱指针域、数据域和后继指针域。
(1)前驱指针域(prve),又称为左链指针,用于存放一个指针,该指针指向上一个结点的开始存储地址。
(2)数据域(data),用于存储该结点的数据元素,数据元素类型由应用问题决定。
(3)后继指针域(next),又称为右链指针,用于存放一个指针,该指针指向下一个结点的开始存储地址。
双向链表的结点结构示意图:
在这里插入图片描述

b. 双链表的基础功能实现代码

  • 创建双链表
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;//该指针指向下一个结点的开始存储地址。
	struct ListNode* prev;//该指针指向上一个结点的开始存储地址。 
	LTDataType data;//用于存储该结点的数据元素
}LTNode;
  • 创建双链表单元
LTNode* BuyListNode(LTDataType x)//创建一个结点
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));//开辟一个结点
	if (!node)//开辟不成功则进入
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;//初始化
	node->next = node->prev = NULL;//初始化
	return node;
}
  • 初始化双链表的哨兵头
LTNode* LTInit(LTDataType x)//初始化
{
	LTNode* phead = BuyListNode(x);
	phead->next = phead;
	phead->prev = phead;
	return phead;//输出的哨兵头单元内的双指针均指向自身
}

在双链表中,初始化双链表的哨兵头函数只用于双链表的哨兵头单元,其它单元不用也可以!

  • 找到pos位置的地址
LTNode* LTEpos(LTNode* phead,int pos)//找到pos位置的地址
{
	LTNode* cur = phead;
	while (pos)
	{
		cur = cur->next;
		pos--;
	}
	return cur;
}
  • 在pos位置前插入
void LTInsert(LTNode* pos, LTDataType x)//在pos位置前插入
{
	assert(pos);
	
	LTNode* newnode = BuyListNode(x);//创建新的单元
	LTNode* prev = pos->prev;//pos前的单元

	prev->next = newnode;//将newnode单元链接到双链表中
	newnode->prev = prev;
	pos->prev = newnode;
	newnode->next = pos;
}

  • 在pos位置删除
void LTErase(LTNode* pos)//在pos位置删除
{
	assert(pos);
	assert(pos->next != pos);//将双链表中的pos位置的单元删除
	LTNode* next = pos->next;//pos位置后的单元
	LTNode* prev = pos->prev;//pos位置前的单元

	next->prev = prev;//链接pos位置的前后单元
	prev->next = next;

	free(pos);
}
  • 在POS位置修改数据
void LTModify(LTNode* pos, LTDataType x)//在POS位置修改数据
{
	assert(pos);
	pos->data = x;//使用指针直接修改就可以
}
  • 尾插
void LTPushBack(LTNode* phead, LTDataType x)//尾插
{
	LTInsert(phead, x);//哨兵头是双链表的头单元,插入到双链表的头单元的前单元就是插入到尾部
}
  • 首插
void LTPushFront(LTNode* phead, LTDataType x)//首插
{
	LTInsert(phead->next, x);//phead后的单元是双链表的首单元位置
}
  • 尾删
void LTPopBack(LTNode* phead)//尾删
{
	LTErase(phead->prev);//双链表的头单元的上个单元是尾,phead->prev是双链表的尾
}
  • 首删
void LTPopFront(LTNode* phead)//首删
{
	LTErase(phead->next);//phead后的单元是双链表的首单元位置
}
  • 遍历打印数组
void LTPrint(LTNode* phead)//遍历打印数组
{
	assert(phead);
	assert(phead->next != phead);//判断是否为空
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}
  • 查找
LTNode* LTFind(LTNode* phead, LTDataType x)//查找
{
	assert(phead);
	assert(phead->next != phead);//判断是否为空
	LTNode* cur = phead->next;//获取首单元
	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}
  • 计算链表长度
size_t LTSize(LTNode* phead)//计算链表长度
{
	assert(phead);
	size_t i = 0;
	LTNode* cur = phead->next;//哨兵头单元不计入链表长度
	while (cur!=phead)
	{
		cur = cur->next;
		i++;
	}
	return i;
}
  • 销毁链表
void LTDestroy(LTNode* phead)//销毁链表
{
	LTNode* left = phead->next;//依次销毁
	LTNode* right = phead->next;
	while (left!=phead)
	{
		right = right->next;
		free(left);
		left = right;
	}
	free(phead);
}

c.双向链表项目文件

c.1 头文件dList.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;

LTNode* BuyListNode(LTDataType x);//创建双链表单元
LTNode* LTInit();
void LTPushBack(LTNode* phead, LTDataType x);//尾插
void LTPopBack(LTNode* phead);//尾删


void LTPushFront(LTNode* phead, LTDataType x);//首插
void LTPopFront(LTNode* phead);//首删

void LTInsert(LTNode* pos, LTDataType x);//在pos位置前插入
void LTErase(LTNode* pos);//在pos位置删除
LTNode* LTEpos(LTNode* phead,int pos);//找到pos位置地址
void LTModify(LTNode* pos, LTDataType x);//在POS位置修改数据

void LTPrint(LTNode* phead);//遍历打印数组
LTNode* LTFind(LTNode* phead, LTDataType x);//查找

size_t LTSize(LTNode* phead);//计算链表长度
void LTDestroy(LTNode* phead);//销毁链表

c.2 函数文件dList.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"dList.h"

LTNode* BuyListNode(LTDataType x)//创建一个节点
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (!node)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;
	node->next = node->prev = NULL;
	return node;
}
LTNode* LTInit(LTDataType x)//初始化
{
	LTNode* phead = BuyListNode(x);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

void LTPushBack(LTNode* phead, LTDataType x)//尾插
{
	LTInsert(phead, x);
}
void LTPopBack(LTNode* phead)//尾删
{
	LTErase(phead->prev);
}


void LTPushFront(LTNode* phead, LTDataType x)//首插
{
	LTInsert(phead->next, x);
}
void LTPopFront(LTNode* phead)//首删
{
	LTErase(phead->next);
}

void LTPrint(LTNode* phead)//遍历打印数组
{
	assert(phead);
	assert(phead->next != phead);//判断是否为空
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

LTNode* LTFind(LTNode* phead, LTDataType x)//查找
{
	assert(phead);
	assert(phead->next != phead);//判断是否为空
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

LTNode* LTEpos(LTNode* phead,int pos)//找到pos位置的地址
{
	LTNode* cur = phead;
	while (pos)
	{
		cur = cur->next;
		pos--;
	}
	return cur;
}

void LTModify(LTNode* pos, LTDataType x)//在POS位置修改数据
{
	assert(pos);
	pos->data = x;
}

void LTInsert(LTNode* pos, LTDataType x)//在pos位置前插入
{
	assert(pos);
	
	LTNode* newnode = BuyListNode(x);
	LTNode* prev = pos->prev;//pos前的单元

	prev->next = newnode;
	newnode->prev = prev;
	pos->prev = newnode;
	newnode->next = pos;
}

void LTErase(LTNode* pos)//在pos位置删除
{
	assert(pos);
	assert(pos->next != pos);
	LTNode* next = pos->next;
	LTNode* prev = pos->prev;

	next->prev = prev;
	prev->next = next;

	free(pos);
}
size_t LTSize(LTNode* phead)//计算链表长度
{
	assert(phead);
	size_t i = 0;
	LTNode* cur = phead->next;
	while (cur!=phead)
	{
		cur = cur->next;
		i++;
	}
	return i;
}

void LTDestroy(LTNode* phead)//销毁链表
{
	LTNode* left = phead->next;
	LTNode* right = phead->next;
	while (left!=phead)
	{
		right = right->next;
		free(left);
		left = right;
	}
	free(phead);
}


c.3 功能实现C文件test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"dList.h"

void test()
{
	int n = 0;//选择功能
	int x = 0;//插入data  ----    x
	int o = 0;//pos位置
	LTNode* pos = NULL;
	LTNode* phead = LTInit(0);//初始化//创建一个节点
	do
	{
		printf("请选择功能>:");
		scanf("%d", &n);
		switch (n)
		{
		case 1://尾插
			printf("请输入一个数据>:");
			scanf("%d", &x);
			LTPushBack(phead, x);
			break;
		case 2://首插
			printf("请输入一个数据>:");
			scanf("%d", &x);
			LTPushFront(phead, x);
			break;
		case 3://尾删
			LTPopBack(phead);
			break;
		case 4://首删
			LTPopFront(phead);
			break;
		case 5://遍历打印
			LTPrint(phead);//遍历打印数组
			break;
		case 6://查找
			printf("请输入要查找的数>:");
			scanf("%d", &x);
			LTNode* newnode = LTFind(phead, x);
			if (newnode != NULL)
				printf("%d\n", newnode->data);
			else
				printf("找不到!!!\n");
			break;
		case 7://修改
			printf("请输入要改变的位置>:");
			scanf("%d", &o);
			printf("请输入将数据改为>:");
			scanf("%d", &x);
			pos = LTEpos(phead, o);
			LTModify(pos, x);//在POS位置修改数据
			break;
		case 8://在pos位置之前插入
			printf("请输入要插入数据的位置>:");
			scanf("%d", &o);
			printf("请输入一个要插入的数据>:");
			scanf("%d", &x);
			pos = LTEpos(phead, o);
			LTInsert(pos, x);
			break;
		case 9://在pos位置删除
			printf("请输入要删除的位置>:");
			scanf("%d", &o);
			pos = LTEpos(phead, o);
			LTErase(pos);//在pos位置删除
			break;
		case 10://链表长度
			printf("链表长度为>:%d\n",LTSize(phead));//计算链表长度
			break;
		case 0://销毁链表
			LTDestroy(phead);
			break;
		default:
			break;
		}
	} while (n);
}

int main()
{
	test();
	return;
}

5. 顺序表与链表的创建与初始化

动态顺序表的创建:

typedef int SLDataType;
typedef struct SeqList
{
	int* a;  //指向动态开辟的数组
	size_t size;  //记录有效数据个数
	size_t capicity; //空间容量大小
}SL;

动态顺序表的初始化:

void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->size = 0;//记录有效数据个数
	ps->capacity = 0;//空间容量大小
	ps->a = NULL;//指向动态开辟的数组
}

无头单向链表的创建:

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

无头单向链表的初始化:

SLTNode* BuySLTNode(SLTDataType x)//初始化独立单元//开辟独立单元
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("maloc fail");
		exit(-1);
	}
	newnode->data = x;//初始化
	newnode->next = NULL;//初始化
	return newnode;
}

有头双向链表:

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;//该指针指向下一个结点的开始存储地址。
	struct ListNode* prev;//该指针指向上一个结点的开始存储地址。 
	LTDataType data;//用于存储该结点的数据元素
}LTNode;

有头双向链表初始化:

双向链表初始化
LTNode* BuyListNode(LTDataType x)//创建一个结点
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));//开辟一个结点
	if (!node)//开辟不成功则进入
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;//初始化
	node->next = node->prev = NULL;//初始化
	return node;
}
初始化双链表的哨兵头
LTNode* LTInit(LTDataType x)//初始化
{
	LTNode* phead = BuyListNode(x);
	phead->next = phead;
	phead->prev = phead;
	return phead;//输出的哨兵头单元内的双指针均指向自身
}

由于带头循环双链表自身结构的特殊性,首尾相连且带有哨兵头单元,所以一个程序有着两种初始化方式,一种是单独给哨兵头单元初始化,使得当双链表为空时,哨兵头的prev指针指向自己,哨兵头的next指针指向自己,使得双链表构成循环。另一种是给除哨兵头单元以外的单元进行的初始化,即node->next = node->prev = NULL//初始化。

6. 顺序表链表优缺点

a.存储空间上的不同

顺序表:物理上一定连续
链表:逻辑上连续,但物理上不一定连续

b.随机访问的复杂度不同

顺序表中存储的元素可以使用数组下标直接访问,无需遍历整个表,因此使用顺序表访问元素的时间复杂度为 O(1);而在链表中访问数据元素,需要从表头依次遍历,直到找到指定节点,花费的时间复杂度为 O(n)。

c.任意位置的插入或者删除的复杂度不同

若想在顺序表pos位置插入一个值,需要将pos及pos位置以后的元素向后搬运,效率低O(N)
若想在链表pos位置插入一个值,只需要修改指针方向即可。

d.扩容的不同

动态顺序表,空间不够时需要扩容。
没有容量的概念。

e.缓存利用率不同

从空间利用率的角度上看,顺序表的空间利用率显然要比链表高。
在这里插入图片描述

在这里插入图片描述

这是因为,链表在存储数据时,每次只申请一个节点的空间,且空间的位置是随机的,如链表图 所示:这种申请存储空间的方式会产生很多空间碎片,一定程序上造成了空间浪费。不仅如此,由于链表中每个数据元素都必须携带至少一个指针,因此,链表对所申请空间的利用率也没有顺序表高。(空间碎片,指的是某些容量很小(1KB 甚至更小)以致无法得到有效利用的物理空间。)

适用的场景

顺序表:元素高效存储+频繁访问
链表:任意位置插入+删除频繁

总结

在这里插入图片描述

评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值