【数据结构初阶】顺序表和链表

1.线性表

        线性表指的是具有部分相同特性的⼀类数据结构的集合,它是由零个或多个数据元素组成的有限序列。
        线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。

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

        顺序表是使用一组连续的存储单元依次存储线性表的数据元素。链表则是通过节点将数据元素链接起来,节点包含数据域和指针域,指针域用于指向下一个节点。

2.顺序表

顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。它使用一组地址连续的存储单元依次存储线性表中的元素。顺序表分为静态顺序表和动态顺序表。

1.静态顺序表

静态顺序表
typedef int Seqdatatype;
#define N 10
typedef struct SeqList
{
	Seqdatatype a[N];  //定长数组
	int size;          //有效数据个数

}sl;

静态顺序表是指采用静态数组来实现的顺序表。它在使用前需要预先分配固定大小的存储空间,一旦分配完成,其容量就不能再改变。
静态顺序表的优点是实现简单,操作直观。
缺点也较为明显:空间大小固定,可能会出现存储空间不足或者浪费的情况。

2.动态顺序表

动态顺序表是在顺序表的基础上,能够根据实际需求动态地调整存储空间大小。它的实现通常基于底层的数组,当现有存储空间不足时,会重新分配一块更大的连续存储空间,并将原数据复制到新的空间中。
 动态顺序表的优点在于:
1. 能够更灵活地适应数据规模的变化,避免了因存储空间固定而导致的数据存储受限问题。
2. 相对于链表,它在随机访问上具有更高的效率。
 不足之处在于:
1. 动态扩展存储空间时,涉及数据的复制操作,会带来一定的时间开销。
2. 频繁的扩展操作可能导致内存碎片的产生。
 在实际应用中,如果需要高效的随机访问,同时数据规模不确定或可能发生较大变化,动态顺序表是一个较好的选择。

typedef struct SeqList
{
	Seqdatatype* arr;
	int size;       //有效数据个数
	int capacity;   //空间大小
}SL;

下面是动态顺序表的实现:

我们将在三个文件中实现动态顺序表,头文件SeqList.h中实现函数的声明,源文件SeqList中实现函数的定义

 text.c :

#include "SeqList.h"

test1()
{
	SL sl;
	SLInit(&sl);

	//尾插
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPrint(sl);
	//头插
	SLPushFront(&sl, 9);
	SLPushFront(&sl, 8);
	SLPushFront(&sl, 7);
	SLPrint(sl);
	//尾删
	SLPopBack(&sl);
	SLPopBack(&sl);
	SLPrint(sl);
	//头删
	SLPopFront(&sl);
	SLPopFront(&sl);
	SLPrint(sl);
	//指定位置
	SLInsert(&sl, 66, 2);
	SLPrint(sl);
	//指定删除
	SLErase(&sl, 1);
	SLPrint(sl);
	//查找数据
	int find = SLFind(&sl, 66);
	if (find < 0)
	{
		printf("没找到\n");
	}
	else
	{
	    printf("找到了\n", find);
	}

	SLDestory(&sl);

}

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

SeqList.h:

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

typedef int SLDataType;

typedef struct SeqList
{
	SLDataType* data;
	int size;
	int Capacity;
}SL;

//初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestory(SL* ps);
//打印
void SLPrint(SL s);
//判断空间充足
void SLCheckCapacity(SL* ps);
//尾插
void SLPushBack(SL* ps , SLDataType x);
//头插
void SLPushFront(SL* ps, SLDataType x);
//尾删
void SLPopBack(SL* ps);
//头删
void SLPopFront(SL* ps);
//指定位置之前插入数据
void SLInsert(SL* ps, SLDataType x,int pos);
//指定位置删除数据
void SLErase(SL* ps, int pos);
//查找数据
int SLFind(SL* ps, SLDataType x);

SeqList.c:  

#include "SeqList.h"

//初始化
void SLInit(SL* ps)
{
	ps->data = NULL;
	ps->size = ps->Capacity = 0;
}

//顺序表的销毁
void SLDestory(SL* ps)
{
	if(ps)
	{
		free(ps->data);
	}
	ps->data = NULL;
	ps->size = ps->Capacity = 0;
}

//打印
void SLPrint(SL s)
{
	for(int i = 0;i <s .size;i++)
	{
		printf("%d ", s.data[i]);
	}
	printf("\n");
}

//判断空间充足
void SLCheckCapacity(SL* ps)
{
	if (ps->size == ps->Capacity)
	{
		int NewCapacity = ps->Capacity == 0 ? 4 : 2 * ps->Capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->data, sizeof(SLDataType) * NewCapacity);
		if (tmp == NULL)
		{
			perror(tmp);
			exit(1);
		}
		ps->data = tmp;
		ps->Capacity = NewCapacity;
	}
}

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	//ps->data[ps->size] = x;
	//ps->size++;
	ps->data[ps->size++] = x;
}

//头插
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	for (int i = ps->size; i >0; i--)
	{
		ps->data[i] = ps->data[i - 1];
	}
	ps->data[0] = x;
	ps->size++;
}

//尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	ps->data[ps->size--];
}

//头删
void SLPopFront(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		ps->data[i] = ps->data[i + 1];
	}
	ps->size--;
}

//指定位置之前插入数据
void SLInsert(SL* ps, SLDataType x,int pos)
{
	assert(ps);
	assert(pos > 0 && pos < ps->size);

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

//指定位置删除数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos > 0 && pos < ps->size);

	for (int i = 0; i < ps->size - pos; i++)
	{
		ps->data[pos - 1 + i] = ps->data[pos + i];
	}
	ps->size--;
}
//查找数据
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);

	for (int i = 0; i < ps->size; i++)
	{
		if (ps->data[i] == x)
		{
			return i;
		}
	}
	return -1;
}

顺序表的优点在于:
 
1. 随机访问效率高,能够在 O(1) 的时间复杂度内访问任意位置的元素。
2. 存储密度高,不需要额外的指针空间来存储元素之间的关系。
 
然而,顺序表也存在一些局限性:
 
1. 插入和删除操作的效率较低。在插入或删除元素时,可能需要移动大量元素来保持顺序。
2. 预先分配的存储空间可能不够用或造成浪费,难以灵活地动态扩展或收缩存储空间。
 
在实际应用中,当需要频繁进行随机访问且数据规模相对稳定时,顺序表是一个不错的选择;而当插入和删除操作较为频繁,或者难以预估数据规模时,链表可能更为合适。

3.链表

1.链表的概念和结构

链表是一种常见的数据结构。
 
链表中的元素称为节点,每个节点包含两部分:数据域和指针域。数据域用于存储节点的数据信息,指针域用于存储指向下一个节点的地址(指针)。
 
链表分为多种类型,可根据带头不带头,单向双向,循环不循环,组合得到八种。

下面介绍的有单链表(不带头不循环)、双向链表(带头不循环)和循环链表。
 
单链表中,每个节点的指针域只指向链表中的下一个节点,尾节点的指针域为空(通常用 NULL 或 nullptr 表示)。
 
双向链表的节点除了有指向下一个节点的指针,还有指向上一个节点的指针,这样可以更方便地实现双向遍历。
 
循环链表则是尾节点的指针不是指向空,而是指向头节点,形成一个环形结构。
 
链表的优点在于:
 
1. 插入和删除操作灵活,不需要像顺序表那样移动大量元素,只需修改相关节点的指针即可。
2. 可以动态地分配内存,适合数据规模不确定的情况。
 
缺点是:
 
1. 不支持随机访问,要访问特定位置的节点,需要从头节点开始依次遍历。
2. 每个节点需要额外的指针空间来存储链接信息,导致存储密度相对较低。
 

2.单链表

在这里介绍的是无头单向非循环链表:结构简单,⼀般不会单独用来存数据。实际中更多是作为其他数据结构的子结构

在单链表中,每个节点包含两部分:数据部分和指针部分。数据部分用于存储具体的数据,指针部分存储下一个节点的地址。
 
单链表的优点是插入和删除操作的时间复杂度较低,只要修改相关节点的指针即可。缺点是无法随机访问,访问特定节点需要从头指针开始依次遍历。
同样是分成三个文件简单实现单链表

text.c:

#include "SList.h"

test1()
{
	SListNode* plist = NULL;
	//尾插
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLPrint(plist);
	//头插
	SLPushFront(&plist, 66);
	SLPushFront(&plist, 55);
	SLPushFront(&plist, 44);
	SLPushFront(&plist, 33);
	SLPrint(plist);
	//尾删
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLPrint(plist);
	//头删
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLPrint(plist);
	//int find = SLFind(plist,2);
	//if (find > 0)
	//{
	//	printf("找到了");
	//}
	//else
	//{
	//	printf("没找到");
	//}
	//指定位置前插后数据
	SLPosBack(&plist, 100, 2);
	SLPrint(plist);
	//指定位置前插入数据
	SLPosFront(&plist, 20, 2);
	SLPrint(plist);
	//删除指定位置的数据
	SLErase(&plist, 100);
	SLPrint(plist);
	//
	SLEraseFront(&plist, 100);
	SLPrint(plist);
}

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

SList.h:

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

typedef int SLDataType;

typedef struct SListNode
{
	SLDataType data;
	struct SListNode* next;
}SListNode;

//打印
void SLPrint(SListNode* phead);
//申请空间
SListNode* SLBuyNode(SLDataType x);
//尾插
void SLPushBack(SListNode** pphead, SLDataType x);
//头插
void SLPushFront(SListNode** pphead, SLDataType x);
//尾删
void SLPopBack(SListNode** pphead);
//头删
void SLPopFront(SListNode** pphead);
//查找数据
int SLFind(SListNode* phead, SLDataType x);
//指定数据之后插入
void SLPosBack(SListNode** pphead, SLDataType x, SLDataType pos);
//指定数据之前插入
void SLPosFront(SListNode** pphead, SLDataType x, SLDataType pos);
//删除指定数据
void SLErase(SListNode** pphead,SLDataType pos);
//删除指定位置之前数据
void SLEraseFront(SListNode** pphead, SLDataType pos);

//销毁链表
void SLDestory(SListNode** pphead);

 SList.c:

#include "SList.h"

//打印
void SLPrint(SListNode* phead)
{
	assert(phead);
	SListNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}
//申请空间
SListNode* SLBuyNode(SLDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror(newnode);
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

//尾插
void SLPushBack(SListNode** pphead, SLDataType x)
{
	assert(pphead);
	SListNode* newnode = SLBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SListNode* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}
//头插
void SLPushFront(SListNode** pphead, SLDataType x)
{
	assert(pphead);
	SListNode* newnode = SLBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

//尾删
void SLPopBack(SListNode** pphead)
{
	assert(pphead && *pphead);

	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	SListNode* ptail = *pphead;
	SListNode* prev = *pphead;

	while (ptail->next)
	{
		prev = ptail;
		ptail = ptail->next;
	}
	free(ptail);
	ptail = NULL;
	prev->next = NULL;

}
//头删
void SLPopFront(SListNode** pphead)
{
	assert(pphead && *pphead);

	SListNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;

}

//查找数据
int SLFind(SListNode* phead, SLDataType x)
{
	assert(phead);
	SListNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return 1;
		}
		pcur = pcur->next;
	}
	return -1;
}

//指定位置之后插入
void SLPosBack(SListNode** pphead, SLDataType x, int pos)
{
	assert(pphead && *pphead);
	assert(pos);

    SListNode* newnode = SLBuyNode(x);
	SListNode* pcur = *pphead;
	while (pcur != NULL)
	{
		if (pcur->data == pos)
		{
			newnode->next = pcur->next;
			pcur->next = newnode;
			return;
		}
		pcur = pcur->next;
	}
	printf("没有找到该数据\n");
}
//指定位置之前插入
void SLPosFront(SListNode** pphead, SLDataType x, int pos)
{
	assert(pphead && *pphead);
	assert(pos);

	if ((*pphead)->data == pos)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SListNode* newnode = SLBuyNode(x);
		SListNode* ptail = *pphead;
		SListNode* prev = *pphead;
		while (ptail != NULL)
		{
			if (ptail->data == pos)
			{
				newnode->next = ptail;
				prev->next = newnode;
				return;
			}
			prev = ptail;
			ptail = ptail->next;
		}
		printf("没有找到该数据\n");
	}
}
//删除指定位置的数据
void SLErase(SListNode** pphead, SLDataType pos)
{
	assert(pphead && *pphead);
	assert(pos);

	if ((*pphead)->data = pos)
	{
		SLPopFront(pphead);
	}
	else
	{
		SListNode* pcur = *pphead;
		SListNode* prev = *pphead;
		while (pcur != NULL)
		{
			if (pcur->data == pos)
			{
				prev->next = pcur->next;
				free(pcur);
				pcur = NULL;
				return;
			}
			prev = pcur;
			pcur = pcur->next;
		}
		printf("没有找到该数据");
	}

}

//删除指定位置之前数据
void SLEraseFront(SListNode** pphead, SLDataType pos)
{
	assert(pphead && *pphead);
	assert(pos);

	if ((*pphead)->data == pos)
	{
		return;
	}
	else
	{
		SListNode* ptail = *pphead;
		SListNode* prev = *pphead;
		while (ptail != NULL)
		{
			if (ptail->next->data == pos)
			{
				prev->next = ptail->next;
				free(ptail);
				ptail = NULL;
				return;
			}
			prev = ptail;
			ptail = ptail->next;
		}
		printf("没有该数据");
	}
}


//销毁链表
void SLDestory(SListNode** pphead)
{
	assert(pphead && *pphead);
	SListNode* pcur = *pphead;
	while (pcur)
	{
		SListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

3.双链表

双链表(Doubly Linked List)是链表的一种更复杂但功能更强大的形式。
 
在双链表中,每个节点不仅有指向下一个节点的指针(称为“后继指针”),还有指向上一个节点的指针(称为“前驱指针”)。
 
双链表的优点在于:
 
1. 可以从链表的头部和尾部两个方向进行遍历,更加灵活。
2. 在查找某个节点的前驱节点时,无需像单链表那样从头开始遍历,直接通过前驱指针即可快速找到,这使得删除节点等操作更加高效。
 
然而,双链表也存在一些缺点:
 
1. 每个节点需要额外存储一个指针,增加了内存开销。
2. 实现的代码相对单链表来说更复杂。
 
在实际应用中,如果需要频繁地在链表中进行前后双向的遍历和操作,双链表是一个更好的选择;如果对内存开销比较敏感,且操作主要集中在单向遍历和处理上,单链表可能更合适。

text.c: 

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include "test.h"

void LT_test1()
{
	//初始化
	//LTNode* plist = NULL;
	//LTInit(&plist);
	LTNode* plist = LTInit();

	//尾插
	LTPushBack(plist, 1);
	LTPrint(plist);
	LTPushBack(plist, 2);
	LTPrint(plist);
	LTPushBack(plist, 3);
	LTPrint(plist);

	//头插
	LTPushFront(plist, 5);
	LTPrint(plist);
	LTPushFront(plist, 6);
	LTPrint(plist);

	尾删
	//LTPopBack(plist);
	//LTPrint(plist);
	//LTPopBack(plist);
	//LTPrint(plist);

	头删
	//LTPopFront(plist);
	//LTPrint(plist);
	//LTPopFront(plist);
	//LTPrint(plist);

	//查找
	LTNode* find = LTFind(plist, 3);
	if (find == NULL)
	{
		printf("找不到\n");
	}
	else
	{
		printf("找到了\n");
	}

	//插入数据
	LTInsert(find, 10);
	find = NULL;
	LTPrint(plist);

	//删除数据
	LTErase(find);
	LTPrint(plist);

	//
	LTDestory(plist);
	plist = NULL;

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

text.h: 

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

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;
//申请节点
LTNode* LTBuyNode(LTDataType x);
//初始化
//void LTInit(LTNode** phead);
LTNode* LTInit();
//打印
void LTPrint(LTNode* phead);
//销毁指针
void LTDestory(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//查找数据
LTNode* LTFind(LTNode* phead, LTDataType x);
//插入数据
void LTInsert(LTNode* pos, LTDataType x);
//删除数据
void LTErase(LTNode* pos);

fun.c: 

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include "test.h"


//申请节点
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	node->data = x;
	node->next = node->prev = node;

	return node;
}
//初始化
//void LTInit(LTNode** pphead)
//{
//	*pphead = LTBuyNode(-1);
//
//}
LTNode* LTInit()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}
//打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

	newnode->prev = phead->prev;
	newnode->next = phead;


	//LTNode* node = phead->prev;
	//node->next = newnode;
	phead->prev->next = newnode;
	phead->prev = newnode;
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

	newnode->prev = phead;
	newnode->next = phead->next;

	//LTNode* node = phead->next;
	//node->prev = newnode;
	phead->next->prev = newnode;
	phead->next = newnode;
	
}

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->prev;

	phead->prev = del->prev;
	//LTNode* node = del->prev;
	//node->next = phead;
	del->prev->next = phead;

	free(del);
	del = NULL;
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->next;

	phead->next = del->next;
	//LTNode* node = del->next;
	//node->prev = phead;
	del->next->prev = phead;

	free(del);
	del = NULL;
}
//查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);

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

	//LTNode* nnode = pos->next;
	//nnode->prev = newnode;
	pos->next->prev = newnode;
	pos->next = newnode;

}
//删除数据
//void LTErase(LTNode* pos)
//{
//	assert(pos);
//
//	pos->prev->next = pos->next;
//	pos->next->prev = pos->prev;
//
//	free(pos);
//	pos = NULL;
//}
void LTErase(LTNode* pos)
{
	assert(pos);

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

	free(pos);
	pos = NULL;
}

//销毁指针
void LTDestory(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值