0
点赞
收藏
分享

微信扫一扫

顺序表与链表

瑾谋 2022-02-14 阅读 80

目录

1. 顺序表

1.1 顺序表的概念及其结构

1.2 顺序表的插入(头插,尾插,插入指定位置)

1.3 顺序表的删除(头删,尾删,删除指定位置)

1.4 顺序表的查找

1.5 顺序表的接口实现(供大家考察是否掌握)

2. 链表

2.1 链表的概念及其结构

2.2 单链表的插入(头插,尾插,指定位置插入)

2.3 单链表的删除(头删,尾删,指定位置删除)

2.4 单链表的查找

2.5 单链表的接口实现(供大家考察是否掌握)

3. 顺序表与链表的优缺点

3.1 存取方式

3.2 逻辑结构与物理结构

3.3 时间性能

3.4 空间性能


1. 顺序表


1.1 顺序表的概念及其结构

    基本概念:

  • 存储空间连续,既允许元素的顺序访问,又可以随机访问
  • 要访问指定元素,可以使用索引(下标)来访问,时间复杂度为O(1);
  • 要在其中增加或者删除一个元素,都要涉及后面所有元素的向前或向后移动,时间复杂度为O(n);
  • 可以方便的存储表中的任一结点,存储速度快;
  • 长度固定,分配内存之前必须知道数组的长度;
  • 无需为表示结点间的逻辑关系而增加额外的存储空间,存储利用率提高

     存储结构:

代码如下:

//顺序表的静态存储
#define N 8
typedef int SLDataType;
typedef struct SeqList
{
  SLDataType array[N];//定长数组
  size_t size;//有效数组的个数
}SeqList;


//顺序表的动态存储
typedef int SLDataType;
typedef struct SeqList
{ 
   SLDataType* array;//指向动态开辟的数组
   size_t size;//有效数据的个数
   size_t capacity;//空间容量的大小
}SeqList;
/*注解:我们发现这里用的是指针,指针是存放一个存储单元地址的.顺序表根据第一个数据元素的地址和数据元素的大小,就可以算出任意数据元素的位置.即只定义第一个元素的指针即可,描述整个顺序表。但它仅仅是个地址,没有确切的空间,因此我们使用是要开辟空间;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity*sizeof(SLDataType));
详细代码下面讲解;*/

静态顺序表的定长数组导致N定大,空间开少了不够用,开多了浪费,所以现实中都是使用动态顺序表,使用倍增-复制的办法来支持动态扩容,将顺序表变成“可变长度”的。下面我将实现动态顺序表;

1.2 顺序表的插入(头插,尾插,插入指定位置)

1)头插法:每一次在顺序表的最前方插入,其他数据后移

void SeqListPushFront(SL* ps, SLDataType x)
{
	//如果没有空间,或者空间不足,我们可以增容
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//对数组进行二倍增容,使用三目运算符是为了防止0这种情况
		SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity);
		//判断是否增容成功
		if(tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
    //从最后一个元素开始向前遍历到第一个元素,分别将它们向后移动一个位置
	for (int i = ps->size - 1; i >= 0; i--)
	{
		ps->a[i + 1] = ps->a[i];
	}
	ps->a[0] = x;//将要插入的数据插入到头部
	ps->size++;//表长加1
}

2)尾插法:顺序表的末尾增加数据,其他元素不用改变

void SeqListPushBack(SL* ps, SLDataType x)
{
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			printf("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->a[ps->size] = x;
	ps->size++;
}

3)指定位置插入:从最后一个元素开始向前遍历到第指定位置,分别将它们向后移动一个位置

void SeqListInsert(SL* ps, int pos, SLDataType x)
{
	//因为顺序表连续存储,所以首先要判断插入位置是否合法;
	assert(pos >= 0 && pos <= ps->size);
	//增容
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	//从最后一个元素开始向前遍历到第指定位置,分别将它们向后移动一个位置
	for (int i = ps->size - 1; i >= pos; i--)
	{
		ps->a[i + 1] = ps->a[i];
	}
    //将x插入到指定位置
	ps->a[pos] = x;
    //表长加1
	ps->size++;
}

1.3 顺序表的删除(头删,尾删,删除指定位置)

1)头删:从删除位置遍历到最后一个元素的位置,将他们依次向前移一个位置;

void SeqListPopFront(SL* ps)
{
	//判断顺序表当前长度是否为0
	assert(ps->size > 0);  

    //从删除位置开始遍历到最后一个元素位置,将他们分别前移一个位置。
	for (int i = 1; i < ps->size; i++)
	{
		ps->a[i - 1] = ps->a[i];
	}
     //表长减一
	ps->size--;
}

2)尾删:将表长减去1即可;

void SeqListPopBack(SL* ps)
{
	assert(ps->size > 0);
	ps->size--;
}

3)删除指定位置:首先判断指定位置是否合法然后从指定位置的下一个元素依次向前移动一步.

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

1.4 顺序表的查找

查找:顺序表的一端开始,依次将每个元素的关键字同给定值 K 进行比较,直到相等或比较完毕还未找到.

int SeqListFind(SL* ps, SLDataType x)
{
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	return -1;
}

1.5 顺序表的接口实现(供大家考察是否掌握)

大家可以参考其提供的接口进行练习考查是否已经掌握.

// 顺序表的动态存储
typedef struct SeqList
{
 SLDataType* array; // 指向动态开辟的数组
 size_t size ; // 有效数据个数
 size_t capicity ; // 容量空间的大小
}SeqList;
// 基本增删查改接口
// 顺序表初始化
void SeqListInit(SeqList* psl, size_t capacity);
// 顺序表销毁
void SeqListDestory(SeqList* psl);
// 顺序表打印
void SeqListPrint(SeqList* psl);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl);
// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* psl);
// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* psl);
// 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x); 
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos);

2. 链表


2.1 链表的概念及其结构

    基本概念:

  •  存储空间不连续,数据元素之间使用指针相连,每个数据元素只能访问周围的一个元素;
  • 长度不固定,可以任意增删;
  • 要访问指定元素,要从头开始遍历元素,直到找到那个元素位置,时间复杂度为O(N)
  • 在指定的数据元素插入或删除不需要移动其他元素,时间复杂度为O(1);

    链表结构: 

2.2 单链表的插入(头插,尾插,指定位置插入)

1)尾插(无头结点):1.创建新结点2.判断链表是否为空3.为空则让头指针指向新结点4.否则找到最后一个指针,指向新结点;

SLTNode* BuyListNode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void SListPushBack(SLTNode** pphead, SLTDateType x)
{
	assert(pphead);
	SLTNode* newnode = BuyListNode(x);//创建新结点
	if (*pphead == NULL)
	{//判断链表是否为空
		*pphead = newnode;
	}
	else {
		//找到最后一个结点
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		//将最后一个结点指向新结点
		tail->next = newnode;
	}
}

 2)头插(无头结点):1.创建新结点 2.新结点指向原链表 3.头指针指向新结点;(注:2,3顺序不能颠倒,否则新结点将找不到原链表的地址)

SLTNode* BuyListNode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void SListPushFront(SLTNode** pphead, SLTDateType x)
{
	SLTNode* newnode = BuyListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;

}

 3)在pos位置之前插入结点:1.创建新结点;2.判断第一个是否是其指定的位置3.如果是再来个头插,否则,找到pos的前一个位置posPrev;4将其新结点插入到pos位置之前,posPrev之后;

SLTNode* BuyListNode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
// 在pos位置之前去插入一个节点
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pphead);
	assert(pos);
	SLTNode* newnode = BuyListNode(x);//创建新结点
    //判断第一个是否等于pos
	if (*pphead == pos)
	{
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else {
        //找到pos的前一个位置
		SLTNode* posprev = *pphead;
		while (posprev->next != pos)
		{
			posprev = posprev->next;
		}
		posprev->next = newnode;
		newnode->next = pos;
	}
}

 4)在pos位置之后插入结点:1.创建新结点; 2.新结点的next指向pos的next;3.pos的next指向newnode;(注:2,3不能交换位置,否则将丢失pos之后的地址)

SLTNode* BuyListNode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void SListInsertAfter(SLTNode* pos, SLTDateType x)
{
	assert(pos);
	SLTNode* newnode = BuyListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

2.3 单链表的删除(头删,尾删,指定位置删除)

1)尾删:1.判断链表是否为空,为空则则停止删除;2.若只有一个结点,释放其结点,让其链表为空;3.若有两个或两个以上的结点,找到最后一个和其前驱;4.其前驱next为空,释放最后一个结点;

void SListPopBack(SLTNode** pphead)
{
	assert(*pphead != NULL);
	//1.一个的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//2.两个或者两个以上
	else {
		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

2)头删: 1.判断链表是否为空;2.头指针指向第一个结点的next;3.释放第一个结点;

void SListPopFront(SLTNode** pphead)
{
	assert(*pphead != NULL);//判断连表是否为空·
	SLTNode* cur = (*pphead)->next;
	free(*pphead);
	*pphead = NULL;
	*pphead = cur;
}

 3)指定位置删除:1.判断链表是否为空;2.如果pos等于第一个结点,则采用头删;3.找出pos的前一个prev 4.将prev的next指向pos的next;5.释放pos;

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SListPopFront(pphead);//头删,详见上面代码
    }
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		} 
		prev->next = pos->next;
		free(pos);
	}
}

2.4 单链表的查找

SLTNode* SListFind(SLTNode* phead, SLTDateType x)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else {
			cur = cur->next;
		}
	}
	return NULL;
}

2.5 单链表的接口实现(供大家考察是否掌握)

// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
 SLTDateType data;
 struct SListNode* next;
}SListNode;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);

3. 顺序表与链表的优缺点


3.1 存取方式

3.2 逻辑结构与物理结构

3.3 时间性能

3.4 空间性能

 以上是顺序表与链表的相关知识点,有不足的地方或者对代码有更好的见解,欢迎评论区留言共同商讨,共同进步!! 

举报

相关推荐

0 条评论