顺序表和链表

线性表

线性表:具有n个相同类型数据元素的有限集合。常见的线性表:顺序表、链表、栈、队列、字符串等。
在这里插入图片描述
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
线性表特点:

  • 集合中存在唯一的“第一个”数据元素。
  • 集合中存在唯一的 “最后一个” 数据元素。
  • 除最后一个元素之外,均有唯一的后继。
  • 除第一个元素之外,均有唯一的前驱。
  • 线性表中元素的个数有限。
  • 线性表中元素的数据类型都相同,每个元素占用相同大小的存储空间。

顺序表

用一组地址连续的存储单元依次存储线性表中的数据元素,使得逻辑上相邻的两个元素在物理位置上也相邻。一般情况下采用数组存储。在数组上完成数据的增删查改。但是线性表中元素的次序是从1开始的,数组中元素的下标是从0开始的

静态顺序表(使用定长数组存储元素)

#define MaxSize 8  //定义线性表的最大长度
typedef int ElemType //ElemType为元素类型
typedef struct SeqList{
    ElemType date[MaxSize];  //顺序表的元素
    int size;  //顺序表的当前长度
}SeqList;  

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

动态顺序表(使用动态开辟的数组存储)

动态分配时,存储数组的空间是在程序执行过程中通过动态存储分配语句分配的,一旦数据空间占满,就会另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充存储数据空间的目的,不需要一次性划分所有空间。

typedef int SLDateType;  //定义数据类型
typedef struct SeqList
{
	SLDateType* a; //动态开辟数组
	int size;      //有效数据个数
	int capacity;  //容量空间的大小
}SeqList;

下面通过代码简单介绍顺序表的初始化、尾插尾删、头插头删、插入指定位置操作、删除指定位置操作以及查询操作,示例代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include <assert.h>
typedef int SLDateType;  //定义数据类型
typedef struct SeqList
{
	SLDateType* a;
	int size;
	int capacity;
}SeqList;
//初始化顺序表
void SeqListInit(SeqList* ps) {
	assert(ps);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}


//销毁顺序表
void SeqListDestroy(SeqList* ps) {
	assert(ps);
	if (ps) {
		free(ps->a);
		ps->a = NULL;
		ps->size = ps->capacity = 0;
	}
}


//打印顺序表
void SeqListPrint(SeqList* ps) {
	assert(ps);
	for (int i = 0; i < ps->size; i++) {
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}


//尾插
//考虑扩容问题
void SeqListCheckCapacity(SeqList* ps) {
	assert(ps);
	if (ps->size == ps->capacity) {
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * newCapacity);
		if (tmp == NULL) {
			perror("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
}
void SeqListPushBack(SeqList* ps, SLDateType x) {
	assert(ps);
	SeqListCheckCapacity(ps);
	ps->a[ps->size] = x;
	ps->size++;
}


//头插
void SeqListPushFront(SeqList* ps, SLDateType x) {
	assert(ps);
	//考虑扩容
	SeqListCheckCapacity(ps);
	//从后向前挪动数据
	int end = ps->size-1;
	while (end >= 0) {
		ps->a[end+1] = ps->a[end];
		end--;
	}
	//实现头插
	ps->a[0] = x;
	ps->size++;
}


//头删
void SeqListPopFront(SeqList* ps) {
	assert(ps);
	//判空
	assert(ps->size > 0);
	//从后向前挪动
	int begin = 1;
	while (begin<ps->size)
	{
		ps->a[begin-1] = ps->a[begin];
		begin++;
	}
	ps->size--;
}


//尾删
void SeqListPopBack(SeqList* ps) {
	assert(ps);
	assert(ps->size > 0);
	ps->a[ps->size - 1] = NULL;
	ps->size--;
}


// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x) {
	assert(ps);
	//循环查找
	for (int i = 0; i < ps->size; i++) {
		if (ps->a[i] == x) {
			return i;
		}
	}
	return -1;
}


// 顺序表在pos位置插入x
//插入考虑扩容问题
void SeqListInsert(SeqList* ps, int pos, SLDateType x) {
	assert(ps);
	assert(pos >= 0);
	assert(pos <= ps->size);
	SeqListCheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= pos) {
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;
	ps->size++;
}


// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos) {
	assert(ps);
	assert(pos >= 0);
	assert(pos < ps->size);
	for (int i = pos + 1; i < ps->size; i++) {
		ps->a[i-1] = ps->a[i];
	}
	ps->size--;
}
void menu() {
	printf("************************\n");
	printf("*****1、尾插数据********\n");
	printf("*****2、尾删数据********\n");
	printf("*****3、头插数据********\n");
	printf("*****4、头删数据********\n");
	printf("*****5、顺序表查找******\n");
	printf("*****6、在pos位置插入x**\n");
	printf("*****7、删除pos位置的值*\n");
	printf("*****8、打印数据********\n");
	printf("************************\n");
}
int main() {
	int input = 0;
	int val = 0;
	int pos = 0;
	SeqList s1;
	SeqListInit(&s1);
	menu();
	do
	{
		printf("请输入你的选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入你要依次尾插的数,以-1结束:");
			scanf("%d ", &val);
			while(val != -1) {
				SeqListPushBack(&s1, val);
				scanf("%d ", &val);
			}
			break;
		case 2:
			SeqListPopBack(&s1);
			break;
		case 3:
			printf("请输入你要头插的数:");
			scanf("%d\n ", &val);
			SeqListPushFront(&s1, val);
			break;
		case 4:
			SeqListPopFront(&s1);
			break;
		case 5:
			printf("请输入你要查找的数:");
			scanf("%d\n ", &val);
			SeqListFind(&s1, val);
			break;
		case 6:
			printf("请输入你要插入的位置和数:");
			scanf("%d %d\n ", &pos,&val);
			SeqListInsert(&s1, pos, val);
			break;
		case 7:
			printf("请输入你要删除的位置:");
			scanf("%d\n ", &pos);
			SeqListErase(&s1, pos);
			break;
		case 8:
			printf("打印数据:");
			SeqListPrint(&s1);
			break;
		default:
			printf("您输入的数字有问题,应在1-7之间");
			break;
		}


	} while (input);
	SeqListDestroy(&s1);

链表

单链表

单链表是指通过一组任意的存储单元来存储线性表中的数据元素。
单链表结点类型表示为:

typedef int SLTDateType;   //定义数据类型
typedef struct SListNode   
{
	SLTDateType data;    //数据域
	struct SListNode* next;   //指针域
}SListNode;

单链表可以解决顺序表需要大量存储单元的缺点,但单链表附加指针域,也存在浪费存储空间的缺点。单链表的存储空间离散地分布在存储空间中,单链表是非随机存取地存储结构。在使用单链表查找数据元素时,不能直接找到某个特定地结点,需要从表头开始遍历,依次比较查找。
下面通过代码简单介绍单链表的初始化、尾插尾删、头插头删、插入指定位置操作、删除指定位置操作以及查询操作,示例代码如下:

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x) {
	SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
	if (newNode == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	newNode->data = x;
	newNode->next = NULL;


	return newNode;
}
// 单链表打印
void SListPrint(SListNode* plist) {
	SListNode* cur = plist;
	while (cur!=NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x) {
	SListNode* newNode = BuySListNode(x);
	//如果链表为空
	if (*pplist == NULL) {
		*pplist = newNode;
	}
	else {
		//不为空,遍历找到最后一个节点
		SListNode* cur = *pplist;
		while (cur->next) {
			cur = cur->next;
		}
		cur->next = newNode;
	}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x) {
	SListNode* newNode = BuySListNode(x);
	newNode->next = *pplist;
	*pplist = newNode;
}
// 单链表的尾删
void SListPopBack(SListNode** pplist) {
	//判空
	assert(*pplist);
	//只有一个链表
	if ((*pplist)->next==NULL) {
		free(*pplist);
		*pplist= NULL;
	}
	else {
		SListNode* cur = *pplist;
		while (cur->next->next)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}
// 单链表头删
void SListPopFront(SListNode** pplist) {
	assert(*pplist);
	if ((*pplist)->next == NULL) {
		free(*pplist);
		*pplist = NULL;
	}
	else {
		//记住头节点下一节点
		SListNode* cur = (*pplist)->next;
		free(*pplist);
		*pplist = cur;
	}
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x) {
	SListNode* cur = plist;
	while (cur) {
		if (cur->data == x) {
			return cur;
		}
			cur = cur->next;
	}
	return NULL;
}


// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x) {
	assert(pos);
	SListNode* newNode = BuySListNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos) {
	assert(pos);
	if (pos->next == NULL) {
		return;
	}
	else
	{
		SListNode* newNode = pos->next;
		pos->next = newNode->next;
		free(newNode);
	}
}
// 单链表的销毁
void SListDestroy(SListNode** pplist) {
	SListNode* cur =* pplist;
	while (cur) {
		SListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pplist = NULL;
}
void menu() {
	printf("**************************\n");
	printf("*****1、尾插数据**********\n");
	printf("*****2、尾删数据**********\n");
	printf("*****3、头插数据**********\n");
	printf("*****4、头删数据**********\n");
	printf("*****5、单链表查找********\n");
	printf("*****6、在pos位置后插入x**\n");
	printf("*****7、删除pos位置后的值*\n");
	printf("*****8、打印数据**********\n");
	printf("**************************\n");
}
int main() {
	int input = 0;
	int val = 0;
	int pos = 0;
	SListNode* plist = NULL;
	menu();
	do
	{
		printf("请输入你的选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入你要依次尾插的数,以-1结束:");
			scanf("%d ", &val);
			while(val != -1) {
				SListPushBack(&plist, val);
				scanf("%d ", &val);
			}
			break;
		case 2:
			SListPopBack(&plist);
			break;
		case 3:
			printf("请输入你要头插的数:");
			scanf("%d\n ", &val);
			SListPushFront(&plist, val);
			break;
		case 4:
			SListPopFront(&plist);
			break;
		case 5:
			printf("请输入你要查找的数:");
			scanf("%d\n ", &val);
			SListFind(plist, val);
			break;
		case 6:
			printf("请输入你要插入位置和数:");
			scanf("%d %d\n ",&pos,&val);
			SListNode* p = SListFind(plist, pos);
			SListInsertAfter(p, val);
			break;
		case 7:
			printf("请输入你要删除的位置:");
			scanf("%d\n ", &pos);
			 p = SListFind(plist,val);
			SListEraseAfter(p);
			break;
		case 8:
			printf("打印数据:");
			SListPrint(plist);
			break;
		default:
			printf("您输入的数字有问题,应在1-7之间");
			break;
		}


	} while (input);
	SListDestroy(&plist);
	return 0;
}

双向链表

单链表结点中只有一个指向其后继的指针,使得单链表只能从头结点依次顺序地向后遍历。要访问某个结点地前驱结点(插入、删除操作)时,只能从头开始遍历,访问后继结点时地时间复杂度为 O(1),访问前驱结点的时间复杂度为 O(n)。
为了克服单链表上的缺陷,引入了双链表,双链表结点中有两个指针prev和next,分别为指向其前驱结点和后继结点。
双链表中结点类型的描述如下:

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;    //数据域
	struct ListNode* _next;   //后继结点
	struct ListNode* _prev;   //前驱结点
}ListNode;

下面通过代码简单介绍双向链表的初始化、尾插尾删、头插头删、插入指定位置操作、删除指定位置操作以及查询操作,示例代码如下:

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}ListNode;
//创建新结点
ListNode* BuyListNode(LTDataType x) {
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (newNode == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	newNode->_data = x;
	newNode->_next = NULL;
	newNode->_prev = NULL;
	return newNode;
}
//链表初始化
ListNode* ListInit() {
	ListNode* pHead = BuyListNode(-1);
	pHead->_next = pHead;
	pHead->_prev = pHead;
	return pHead;
}
// 双向链表打印
void ListPrint(ListNode* pHead) {
	ListNode* cur = pHead->_next;
	while (cur!=pHead)
	{
		printf("%d ", cur->_data);
		cur = cur->_next;
	}
	printf("\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x) {
	ListNode* newNode = BuyListNode(x);
	//找到最后一个节点
	ListNode* tail = pHead->_prev;
	tail->_next = newNode;
	newNode->_prev = tail;

	newNode->_next = pHead;
	pHead->_prev = newNode;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead) {
	assert(pHead);
	assert(pHead->_next != NULL);

	ListNode* tail = pHead->_prev;
	ListNode* prev = tail->_prev;

	prev->_next = pHead;
	pHead->_prev = prev;

	free(tail);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x) {
	ListNode* newNode = BuyListNode(x);
	ListNode* next = pHead->_next;

	pHead->_next = newNode;
	newNode->_prev = pHead;
	newNode->_next = next;
	next->_prev = newNode;
}
// 双向链表头删
void ListPopFront(ListNode* pHead) {
	assert(pHead);
	assert(pHead->_next!=pHead);
	
	ListNode* first = pHead->_next;
	ListNode* second = first->_next;

	free(first);
	pHead->_next = second;
	second->_prev = pHead;
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x) {
	ListNode* cur = pHead->_next;
	while (cur != pHead) {
		if (cur->_data == x) {
			return cur;
		}
			cur = cur->_next;
	}
	return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) {
	ListNode* newNode = BuyListNode(x);
	ListNode* prev = pos->_prev;
	prev->_next = newNode;
	newNode->_prev = prev;
	newNode->_next = pos;
	pos->_prev = newNode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) {
	ListNode* next = pos->_next;
	ListNode* prev = pos->_prev;
	free(pos);
	prev->_next = next;
	next->_prev = prev;
}
// 双向链表销毁
void ListDestory(ListNode* pHead) {
	ListNode* cur = pHead->_next;
	while (cur != pHead) {
		ListNode* next = cur->_next;
		free(cur);
		cur = next;
	}
	free(pHead);
}

顺序表和链表区别

不同点顺序表链表
存储空间物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持:O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低O(N)只需修改指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
缓存利用率
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值