【数据结构】顺序表与链表

目录

一、线性表

二、顺序表

2.1 概念与结构

2.2 动态顺序表的接口实现

1. 动态顺序表结构体定义

2. 顺序表的动态初始化

3. 顺序表的打印

4. 顺序表内存检查

4. 顺序表的尾插与头插

5. 顺序表的尾删与头删

6. 顺序表中间插入数据与删除数据

7. 顺序表数据查找,并返回下标

8. 修改顺序表指定数据

9. 顺序表的销毁

2.3 动态顺序表的声明及实现代码

1. SeqList.h:顺序表结构体声明、函数声明以及头文件引用

2. SeqList.c:各个函数的实现

 2.4 顺序表的问题及思考

三、链表

3.1 链表的概念及结构

3.2 链表的分类

1. 单项或双向

2. 带头或不带头

3. 循环或非循环

3.3 无头单向非循环链表的接口实现


一、线性表

线性表是n个具有相同特性的数据元素的有限序列。

常见的线性表有:顺序表、链表、栈、队列、字符串等。

线性表在逻辑上是线性结构,也就是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

 

二、顺序表

2.1 概念与结构

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

 顺序表一般可分为:

1.静态顺序表:使用定长数组存储元素

#define N 8 //静态顺序表内存大小
typedef int SLDataType;//顺序表数据类型

typedef struct SeqList
{
	SLDataType arr[N]; //定长数组
	size_t size; //顺序表有效数据个数
}SeqList;

2.动态顺序表:使用动态开辟的数组存储

typedef int SLDataType;//顺序表数据类型

typedef struct SeqList
{
	SLDataType* array; //指向动态开辟的数组
	size_t size; // 顺序表有效数据个数
	size_t capacity; //当前顺序表空间大小,如果不够则使用动态内存增容
}SeqList;

2.2 动态顺序表的接口实现

1. 动态顺序表结构体定义

顺序表的两种结构应用的场景不同,静态顺序表只适用于确定存储数据大小的时候,而我们日常使用的是动态顺序表,可以根据需要扩大内存空间

//动态顺序表
typedef int SLDatatype;//方便变更数据类型
typedef struct SeqList
{
	SLDatatype *a;
	int size;		//存储的有效数据的个数
	int capacity;	//容量
}SL;

2. 顺序表的动态初始化

 

//动态初始化
void SLInit(SL* psl)
{
	assert(psl);
	psl->a = (SLDatatype*)malloc(sizeof(SLDatatype)*4);
	if (psl->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	psl->size = 0;//初始顺序表的数据个数为0
	psl->capacity = 4;//初始顺序表的内存大小设为4,可根据实际来决定初始内存大小
}

3. 顺序表的打印

与数组的打印方式相同,依次打印顺序表数据

//打印
void SLPrint(SL* psl)
{
	assert(psl);
	for (int i = 0; i < psl->size; i++)
	{
		printf("%d ", psl->a[i]);
	}
	printf("\n");
}

4. 顺序表内存检查

在数据插入顺序表时需要使用,如果实际数据个数等于内存大小,则需要增容,每次增容个数可以根据实际设置

//检查空间,不够则增容
void SLCheckCapacity(SL* psl)
{
	assert(psl);
	if (psl->size == psl->capacity)
	{
		SLDatatype* tmp = 
			(SLDatatype*)realloc(psl->a, sizeof(SLDatatype) * psl->capacity * 2);
                //每次再开辟原空间内存大小的内存
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		psl->a = tmp;
		psl->capacity *= 2;
	}
}

4. 顺序表的尾插与头插

//尾插
void SLPushBack(SL* psl, SLDatatype x)
{
	assert(psl);
    //SLInsert(psl, psl->size, x);
    //在实现了顺序表中间插入数据后,可以使用该代码代替下面几行代码
	SLCheckCapacity(psl);
	psl->a[psl->size] = x;
	psl->size++;
}
//头插
void SLPushFront(SL* psl, SLDatatype x)
{
	assert(psl);
	//SLInsert(psl, 0, x);//在实现顺序表中间插入数据接口函数后可以使用该行代码代替下面代码
	SLCheckCapacity(psl);
	//从后往前挪动
	//memmove(psl->a + 1, psl->a, sizeof(SLDatatype) * psl->size);//内存移动
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->a[end + 1] = psl->a[end];
		end--;
	}
	psl->a[0] = x;
	psl->size++;

}

5. 顺序表的尾删与头删

//尾删
void SLPopBack(SL* psl)
{
	assert(psl);
	//SLErase(psl, psl->size - 1);//实现指定数据删除函数后可使用该代码代替下面几行代码
	//暴力检查
	assert(psl->size > 0);
	psl->size--;


}
//头删
void SLPopFront(SL* psl)
{
	assert(psl);
	//SLErase(psl, 0);//顺序表删除指定数据函数辅助实现
	assert(psl->size > 0);

	//memmove(psl->a, psl->a+1, sizeof(SLDatatype) * psl->size);//可用内存函数实现
	int start = 0;
	while (start < psl->size - 1)
	{
		psl->a[start] = psl->a[start + 1];
		start++;
	}
	psl->size--;
}

6. 顺序表中间插入数据与删除数据

//中间插入数据
void SLInsert(SL* psl, int pos, SLDatatype x)
{
	assert(psl);
	assert(pos >= 0 && pos <= psl->size);
	SLCheckCapacity(psl);
	//memmove(psl->a + pos + 1, psl->a + pos, sizeof(SLDatatype)*(psl->size - pos));
    //内存函数
	int end = psl->size - 1;
	while (end >= pos)
	{
		psl->a[end + 1] = psl->a[end];
		--end;
	}
	psl->a[pos] = x;
	psl->size++;
}
//中间删除数据
void SLErase(SL* psl, int pos)
{
	assert(psl);
	assert(pos >= 0 && pos < psl->size);
	//memmove(psl->a + pos, psl->a + pos + 1, sizeof(SLDatatype) * (psl->size - pos))
    //内存函数
	int start = pos + 1;
	while (start < psl->size)
	{
		psl->a[start - 1] = psl->a[start];
		start++;
	}
	psl->size--;
}

7. 顺序表数据查找,并返回下标

//返回第一个符合的指定数据的下标,没有返回-1
int SLFind(SL* psl, SLDatatype x)
{
	assert(psl);
	for (int i = 0; i < psl->size; i++)
	{
		if (psl->a[i] == x)
			return i;
	}
	return -1;
}

8. 修改顺序表指定数据

//修改指定数据内容
void SLModify(SL* psl, int pos, SLDatatype x)
{
	assert(psl);
	assert(pos >= 0 && pos < psl->size);

	psl->a[pos] = x;
}

9. 顺序表的销毁

//销毁
void SLDestroy(SL* psl)
{
	assert(psl);
	free(psl->a);
	psl->a = NULL;
	psl->size = 0;
	psl->capacity = 0;
}

2.3 动态顺序表的声明及实现代码

1. SeqList.h:顺序表结构体声明、函数声明以及头文件引用

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

//动态顺序表
typedef int SLDatatype;//方便变更数据类型
typedef struct SeqList
{
	SLDatatype *a;
	int size;		//存储的有效数据的个数
	int capacity;	//容量
}SL;

void SLInit(SL* psl);//顺序表的初始化
void SLDestroy(SL* psl);//顺序表的内存销毁

void SLPrint(SL* psl);//打印

void SLPushBack(SL* psl, SLDatatype x);//尾插
void SLPushFront(SL* psl, SLDatatype x);//头插

void SLPopBack(SL* psl);//尾删
void SLPopFront(SL* psl);//头删

//中间插入数据
void SLInsert(SL* psl, int pos, SLDatatype x);
//中间删除数据
void SLErase(SL* psl, int pos);

//找到返回下标,没有返回-1
int SLFind(SL* psl, SLDatatype x);

//修改指定数据内容
void SLModify(SL* psl, int pos, SLDatatype x);

2. SeqList.c:各个函数的实现

#define _CRT_SECURE_NO_WARNINGS  1
#include "SeqList.h"

//动态初始化
void SLInit(SL* psl)
{
	assert(psl);
	psl->a = (SLDatatype*)malloc(sizeof(SLDatatype)*4);
	if (psl->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	psl->size = 0;
	psl->capacity = 4;
}

//销毁
void SLDestroy(SL* psl)
{
	assert(psl);
	free(psl->a);
	psl->a = NULL;
	psl->size = 0;
	psl->capacity = 0;
}

//打印
void SLPrint(SL* psl)
{
	assert(psl);
	for (int i = 0; i < psl->size; i++)
	{
		printf("%d ", psl->a[i]);
	}
	printf("\n");
}

//检查空间,不够则增容
void SLCheckCapacity(SL* psl)
{
	assert(psl);
	if (psl->size == psl->capacity)
	{
		SLDatatype* tmp = 
			(SLDatatype*)realloc(psl->a, sizeof(SLDatatype) * psl->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		psl->a = tmp;
		psl->capacity *= 2;
	}
}

//尾插
void SLPushBack(SL* psl, SLDatatype x)
{
	assert(psl);
	//SLCheckCapacity(psl);
	//psl->a[psl->size] = x;
	//psl->size++;
	SLInsert(psl, psl->size, x);
}

//头插
void SLPushFront(SL* psl, SLDatatype x)
{
	assert(psl);
	//SLCheckCapacity(psl);

	从后往前挪动
	memmove(psl->a + 1, psl->a, sizeof(SLDatatype) * psl->size);//内存移动
	//int end = psl->size - 1;
	//while (end >= 0)
	//{
	//	psl->a[end + 1] = psl->a[end];
	//	end--;
	//}
	//psl->a[0] = x;
	//psl->size++;
	SLInsert(psl, 0, x);
}

//尾删
void SLPopBack(SL* psl)
{
	assert(psl);
	//暴力检查
	//assert(psl->size > 0);
	//温柔检查
	//if (psl->size == 0)
	//{
	//	return;
	//}
	//psl->size--;

	SLErase(psl, psl->size - 1);
}

//头删
void SLPopFront(SL* psl)
{
	assert(psl);
	//assert(psl->size > 0);

	//memmove(psl->a, psl->a+1, sizeof(SLDatatype) * psl->size);
	//int start = 0;
	//while (start < psl->size - 1)
	//{
	//	psl->a[start] = psl->a[start + 1];
	//	start++;
	//}

	//int start = 1;
	//while (start < psl->size)
	//{
	//	psl->a[start - 1] = psl->a[start];
	//	start++;
	//}

	//psl->size--;

	SLErase(psl, 0);
}

//中间插入数据
void SLInsert(SL* psl, int pos, SLDatatype x)
{
	assert(psl);
	assert(pos >= 0 && pos <= psl->size);
	SLCheckCapacity(psl);
	//memmove(psl->a + pos + 1, psl->a + pos, sizeof(SLDatatype)*(psl->size - pos));
	int end = psl->size - 1;
	while (end >= pos)
	{
		psl->a[end + 1] = psl->a[end];
		--end;
	}
	psl->a[pos] = x;
	psl->size++;
}

//中间删除数据
void SLErase(SL* psl, int pos)
{
	assert(psl);
	assert(pos >= 0 && pos < psl->size);
	//assert(psl->size > 0);//上面这条可以检测数据个数大于0
	
	//memmove(psl->a + pos, psl->a + pos + 1, sizeof(SLDatatype) * (psl->size - pos));

	int start = pos + 1;
	while (start < psl->size)
	{
		psl->a[start - 1] = psl->a[start];
		start++;
	}

	psl->size--;
}

//找到返回下标,没有返回-1
int SLFind(SL* psl, SLDatatype x)
{
	assert(psl);
	for (int i = 0; i < psl->size; i++)
	{
		if (psl->a[i] == x)
			return i;
	}
	return -1;
}

//修改指定数据内容
void SLModify(SL* psl, int pos, SLDatatype x)
{
	assert(psl);
	assert(pos >= 0 && pos < psl->size);

	psl->a[pos] = x;
}

 2.4 顺序表的问题及思考

问题:

1.数据的插入删除,时间复杂度为O(N)

2.增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗。

3.在这里我们设置增容呈2倍大小的空间增长,势必会有一定的空间浪费。如果我们新申请了N个容量,但我们只新插入了1个数据,那么就会有N-1个空间被浪费

 相比较于顺序表的连续空间带来的不便,链表是如何实现的呢

三、链表

3.1 链表的概念及结构

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

1.从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续

2.现实中的节点一般都是从堆上申请出来的

3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续也可能不连续

3.2 链表的分类

链表的结构共计有8种:

1. 单项或双向

2. 带头或不带头

3. 循环或非循环

 在我们日常使用中,一般是两种结构:无头单向非循环链表、带头双向循环链表

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。这个结构在实现时会出现内存越界等问题
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。这个结构虽然复杂,但是使用起来会带来很多优势,实现起来反而简单

3.3 无头单向非循环链表的接口实现

和动态顺序表的基本功能相同,但实际使用中函数使用的是结构体指针的地址,相较于顺序表中使用的数组的地址,这里的二级指针会造成很大不便,需要注意

1. Slist.h:头文件声明、函数声明及链表结构体的定义

#pragma once

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

typedef int SLTDatatype;
typedef struct SList
{
	SLTDatatype data;//数据域
	struct SList* next;//指针域
}SLTNode;

//打印链表
void SLTPrint(SLTNode* phead);

//头插
void SLTPushFront(SLTNode** pphead, SLTDatatype x);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDatatype x);

//头删
void SLTPopFront(SLTNode** pphead);
//尾删
void SLTPopBack(SLTNode** pphead);

//单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDatatype x);

//在pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDatatype x);

//在pos之后插入
void SLTInsertAfter(SLTNode* pos, SLTDatatype x);

//删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos);

//删除pos后面的值
void SLTEraseAfter(SLTNode* pos);

//单链表销毁
void SLTDestroy(SLTNode** phead);

2. Slist.c:各接口实现

#define _CRT_SECURE_NO_WARNINGS  1
#include "SList.h"

void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

//创建节点
SLTNode* BuyLTNode(SLTDatatype x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;//返回的是指针,是地址
}

//头插
void SLTPushFront(SLTNode** pphead, SLTDatatype x)
{
	assert(pphead);//链表为空,pphead也不为空,因为他是头指针plist的地址
	//assert(*pphead);//不能断言,链表为空,也需要能插入

	SLTNode* newnode = BuyLTNode(x);
	
	newnode->next = *pphead;
	*pphead = newnode;
}

//尾插

//void SLTPushBack(SLTNode* phead, SLTDatatype x)
//{
//	SLTNode* tail = phead;
//	while (tail != NULL)//tail成为空指针
//	{
//		tail = tail->next;
//	}
//	SLTNode* newnode = BuyLTNode(x);
//	tail = newnode;
//	//地址拷贝,函数调用结束后,tail会销毁,同时没有吧尾部插入数据连接起来,造成内存泄露
//}

void SLTPushBack(SLTNode** pphead, SLTDatatype x)
{
	SLTNode* newnode = BuyLTNode(x);
	//1.空链表
	//2.非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;//拷贝完之后调用函数结束phead不返回值给plist
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)//找到尾节点
		{
			tail = tail->next;
		}
		/* SLTNode* */tail->next = /* SLTNode* */newnode;
		//旧尾节点的指针域指向新尾节点的地址
	}
}

//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead); //链表为空,pphead也不为空,因为他是头指针plist的地址
	//空链表
	assert(*pphead);//链表为空,不能头删(可以使用温柔的检查,返回空指针)
	//只有一个节点
	//if ((*pphead)->next == NULL)
	//{
	//	free(*pphead);
	//	*pphead = NULL;
	//}
	//else
	//{
	//	//保存下一个,然后free掉
	//	//或者保存当前节点,然后指向下一个,把当前保存的free
	//	SLTNode* del = *pphead;
	//	//*pphead = del->next;
	//	*pphead = (*pphead)->next;
	//	free(del);
	//}

	//只要有一个节点,让pphead指向下一个节点,把当前节点free
	SLTNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
}


//尾删
void SLTPopBack(SLTNode** pphead)
{
	//没有节点(空链表)
	//暴力的检查
	assert(*pphead);
	//温柔的检查
	//if (*pphead == NULL)
	//{
	//	return;
	//}
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//找到尾节点,释放尾节点并把指向尾节点的倒数第二个节点的指针域置为空指针
		//SLTNode* prev = NULL;
		//SLTNode* tail = *pphead;
		while (tail->next != NULL)
		//while (tail->next)
		//{
		//	prev = tail;
		//	tail = tail->next;
		//}

		//free(tail);
		//prev->next = NULL;

		//直接找倒数第二个节点
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}

		free(tail->next);
		tail->next = NULL;
	}
}

//单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDatatype x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

//在pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDatatype x)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

//在pos之后插入
void SLTInsertAfter(SLTNode* pos, SLTDatatype x)
{
	assert(pos);

	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

//删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

//删除pos位置后面的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}

//单链表销毁
void SLTDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值