数据结构--顺序表与单链表的实现

前言

现在给大家介绍线性表中两个常见的结构顺序表和链表,其中链表又包括单链表和带头双向循环链表,都是用来存储数据的一种结构

1.线性表

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

1.1. 顺序表

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

两种顺序表

第一种为静态顺序表:

typedef int SLDataType;
typedef struct SeqList
{
	SLDataType array[100];
	size_t size;//数据下标位置
}SeqList;

第二种为动态循序表:

typedef int SLDataType;
typedef struct SeqList
{
   SLDataType* arr;
   size_t size;//数据下表位置
   size_t capacity//容量的大小
}SeqList;

一般情况下,都是去实现动态顺序表,因为静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中都是使用动态顺序表。

动态顺序表实现

既然是数据结构,当然要创建文件要规范。
在这里插入图片描述
test.c用来测试,sequencelist.c用来完成顺序表的增删尾插功能,sequencelist.h用来声明包含头文件

sequencelist.h:

#pragma once//防止头文件重复被包含

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

typedef int SeqListTypeData;

typedef struct SeqList
{
	SeqListTypeData* arr;
	int size;
	int capacity;
}SeqList;

void SeqListInit(SeqList* ps);//初始化

void SeqListDestroy(SeqList* ps);//销毁顺序表

void SeqListPrint(SeqList* ps);//打印出顺序表的数据

void SeqListPushBack(SeqList* ps, const SeqListTypeData val);//尾插数据

void SeqListPushFront(SeqList* ps, const SeqListTypeData val);//头插数据

void SeqListPopFront(SeqList* ps);//头删数据

void SeqListPopBack(SeqList* ps);//尾删数据

int SeqListFind(SeqList* ps, const SeqListTypeData val);//寻找数据

void SeqListInsert(SeqList* ps, int pos, const SeqListTypeData val);
//在pos位置上插入数据
void SeqListErase(SeqList* ps, int pos);
//删除pos位置上的数据
顺序表的初始化
void SeqListInit(SeqList* ps)
{
	assert(ps);//防止NULL被解引用
	ps->arr = NULL;
	ps->capacity = 0;
	ps->size = 0;
}
顺序表的销毁

当程序结束前时,既然向堆区申请空间,就是去释放。

void SeqListDestroy(SeqList* ps)
{
	assert(ps);
	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
顺序表的打印
void SeqListPrint(SeqList* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
}
顺序表的头插和尾插数据

注意:插入数据时,需要考虑的数组的空间,是否需要扩容。

检查空间是否够大
void Create_Capacity(SeqList* ps)//数组空间满了,扩容
{
	int newcapacity = (ps->capacity == 0) ? 4 : ps->capacity * 2;
	//利用三目操作符来确认扩容的大小
	SeqListTypeData* tmp = (SeqListTypeData*)realloc(ps->arr, newcapacity * sizeof(SeqListTypeData));

	if (tmp == NULL)//申请空间失败
	{
		perror("realloc");
		exit(1);//退出
	}
	ps->arr = tmp;//原数组继承扩容后的空间大小,数据不变
	ps->capacity = newcapacity;
}

void Check_capacity(SeqList* ps)
{
	assert(ps);
	if (ps->capacity == ps->size)//空间已满
	{
		Create_Capacity(ps);//调用扩容函数,进行扩容
	}
}
尾插
void SeqListPushBack(SeqList* ps, const SeqListTypeData val)
{
	assert(ps);
	Check_capacity(ps);//检查空间
	ps->arr[ps->size++] = val;
}
头插
void SeqListPushFront(SeqList* ps, const SeqListTypeData val)
{
	assert(ps);
	Check_capacity(ps);//检查空间

	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->size++;
	ps->arr[0] = val;
}

注意:时间复杂度为O(N),在进行头插的时候,需要注意挪动位置,防止越界

顺序表的尾删和头删数据

注意:在头删和尾删时,要注意是否顺序表中的ps->arr是否为0。

头删
void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);//防止当顺序表为空时,继续删,使数组越界

	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;

}

头删跟头插的时间复杂度一样为O(N),且一样挪动数据时,需要注意要挪动数据的下标位置。

尾删
void SeqListPopBack(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);

	ps->size--;
}
定位删除和插入
int SeqListFind(SeqList* ps, const SeqListTypeData val)
{
	assert(ps);
	
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == val)
		{
			return i;//返回下标位置pos为i
		}
	}
	return -1;//没有找到该位置
}

void SeqListInsert(SeqList* ps, int pos, const SeqListTypeData val)
{
	assert(ps);

	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = val;
	ps->size++;
}

void SeqListErase(SeqList* ps, int pos)
{
	assert(ps);
	assert(pos < ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;

}

配合SeqListFind(ps,val)返回的值为要寻找的pos位置,去进行该pos位置的删除或插入

1.2 链表

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

结构:在这里插入图片描述
注意:

  1. 从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
  2. 现实中的结点一般都是从堆上申请出来的
  3. 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
  4. 图中的线是为了更好的理解链式结构,实际上没有链接线

链表的分类

1.单向或者双向
在这里插入图片描述
2.带头或者不带头
在这里插入图片描述
3.循环或者非循环
在这里插入图片描述
虽然有这么多的链表结构,但是我们实际中最常用的还是以下两种结构:

在这里插入图片描述

无头单向非循环链表

实现的功能:

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

typedef int STLTypeData;

typedef struct ListNode
{
	struct ListNode* next;
	STLTypeData val;
}STL;


STL* CreateNode(STLTypeData val);
// 动态申请一个结点
void STLPushBack(STL** plist, STLTypeData val);
// 单链表尾插
void PrintfSList(STL* plist);
// 单链表打印
void DestroyALLNode(STL** plist);
//销毁单链表
void STLPushFront(STL** plist, STLTypeData val);
// 单链表的头插
void STLPopFront(STL** plist);
// 单链表头删
void STLPopBack(STL** plist);
// 单链表的尾删
void STLInsert(STL** plist, STL* position, STLTypeData val);
// 在pos之前插入x
STL* STLFind(STL** plist, STLTypeData val);
// 单链表查找
void STLErase(STL** plist, STL* position);
// 删除pos位置
申请结点
STL* CreateNode(STLTypeData val)
{
	STL* tmp = (STL*)malloc(sizeof(STL));
	if (tmp == NULL)//申请空间失败
	{
		perror("malloc fail");
		exit(1);
	}
	tmp->val = val;
	tmp->next = NULL;
	return tmp;
}
单链表的头插
void STLPushFront(STL** plist, STLTypeData val)
{
	assert(*plist);//防止 plist 为 NULL;
	STL* Next = *plist;
	*plist = CreateNode(val);
	(*plist)->next = Next;
}

有没有发现链表的头插的时间复杂度是O(1),而顺序表头插的时间复杂度是O(N)。

单链表尾插
void STLPushBack(STL** plist, STLTypeData val)
{
	if (*plist == NULL)//链表为空,创建一个新结点
	{
		*plist = CreateNode(val);
	}
	else
	{
		STL* TailNode = *plist;
		while (TailNode->next)//终止条件是临时指针指向NULL的上一个
		{
			TailNode = TailNode->next;
		}
		TailNode->next = CreateNode(val);
	}
	return;//可有可无
}
单链表头删
void STLPopFront(STL** plist)
{
	assert(*plist);

	STL* Next = *plist;
	*plist = (*plist)->next;

	free(Next);
}
单链表尾删
void STLPopBack(STL** plist)
{
	assert(*plist);
	STL* tailnode = *plist;
	STL* prev = NULL;
	while (tailnode->next)
	{
		prev = tailnode;
		tailnode = tailnode->next;
	}
	if (prev == NULL)
	{
		free(tailnode);
		tailnode = NULL;
		*plist = NULL;
	}
	else
	{
		free(tailnode);
		prev->next = NULL;
	}
}
单链表打印
void PrintfSList(STL* plist)
{
	assert(plist);
	STL* cur = plist;
	while (cur)
	{
		printf("->%d", cur->val );
		cur = cur->next;
	}
	//printf("NULL");
}
单链表查找
STL* STLFind(STL** plist, STLTypeData val)
{
	assert(plist);
	STL* cur = *plist;
	while (cur)
	{
		if (cur->val == val)
		{
			return cur;//返回目标结点
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;//没有找到结点,返回NULL;
}
在pos之前插入x
void STLInsert(STL** plist, STL* position, STLTypeData val)
{
	if (*plist == position)
	{
		STLPushFront(plist, val);
	}
	else
	{
		STL* posprev = *plist;
		while (posprev->next != position)
		{
			posprev = posprev->next;
		}

		STL* newnode = CreateNode(val);
		newnode->next = posprev->next;
	    posprev->next = newnode;
	}
}
删除pos位置
void STLErase(STL** plist, STL* position)
{
	assert(*plist);

	if (*plist == position)
	{
		STLPopFront(plist);
	
	}
	else
	{
		STL* prev = *plist;
		while (prev->next != position)
		{
			prev = prev->next;
		}

		prev->next = position->next;
		free(position);
	}
}
销毁单链表
void DestroyALLNode(STL** plist)
{
	assert(*plist);
	STL* cur = *plist;
	while (cur)
	{
		STL* prev = cur;
		cur = cur->next;
		free(prev);
	}
	*plist = NULL;//防止野指针
}

结尾

由于篇幅过长,带头双向循环链表下一篇博客实现,感谢大家阅读,一起努力,加油!!!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值