【数据结构与算法】顺序表与链表(单链表和双链表)超详解图示与源码。

                                                  

大家好,今天我们来学习数据结构中的顺序表与链表!源码在最后附上

首先我们先来认识一下顺序表

                                     

**如上图所示:很多人会以为数组就是顺序表,顺序表就是数组,这种理解时错误的。

数组:

数组是相同数据类型的元素按一定顺序排列的的集合。数组中的元素存储在一个连续性的内存块中,并通过索引来访问。

简单的说,数组是在物理空间中连续存储的相同数据类型的元素的集合

而顺序表:

顺序表,全名顺序存储结构,是线性表的一种。(线性表(linear list)是数据结构的一种,一个线性表是 n 具有相同特性的数据元素的有限序列。线性表在逻辑上是线性结构,也就说是连续的一条直线,但是在物理结构上并不一定是连续的。常见的线性表:顺序表、链表、栈、队列、字符串等。

顺序表不仅要求数据在逻辑上是连续的一条直线,还要求用一段物理地址连续的存储单元以存储表中数据元素,一般情况下采用数组存储。

总结:

顺序表是在计算机内存中以数组的形式保存的线性表,只能从头开始连续存储。
此处将「数组」理解为物理结构,「顺序表」理解为逻辑结构比较合理
我们可以用数组实现顺序表,但我们同样可以用数组实现二叉树、队列等结构,因此不能直接认为顺序表就是数组
在顺序表中我们可以定义表中元素个数、表空间大小等变量,在数组中是没有的

                     

首先我们来学习一下顺序表的静态存储

                                         

而顺序表的静态存储又存在明显的缺陷和不足

不知道需要给确定的多少个空间,N给小了不够用,N给大了会浪费。

那么如何解决这个问题呢?

在这里我们使用malloc来动态开辟空间,再使用realloc来进行空间的管理

顺序表的动态存储:

                                   

在这里我们要注意realloc扩容存在异地扩容原地扩容俩种情况;

注意地址,当你realloc的空间没有那么大时,这里就是原地扩容

                         

注意地址,当你realloc的空间很大的时候,就会异地扩容

          

这里先让我们简单的来实现顺序表的几个功能:(完整功能最后附上源码)

实现顺序表的头插,即往前边插进去数据:

 

实现顺序表中间插入数据

实现顺序表的删除

通过以上学习,我们大概了解了顺序表的一些基础知识,那么顺序表是否存在一些缺点呢?

顺序表的缺点

1.尾部插入数据还不错,但是头部和中间插入删除数据,效率低下,需要挪动数据。 

2.满了之后只能扩容,而扩容是有一定的消耗的,扩容一般是存在一定的空间浪费(假如空间100已经满了,扩容到200,只需要插入120个数据,那么就有80个浪费掉了)

3.一次扩的多了,可能浪费掉了;一次扩的少了,有需要频繁去扩容。

                                                      

接下来让我们来学习链表 

链表:链表采用链式存储结构,其内存空间不连续

而链表分为单链表双链表,我们先来学习单链表

          

                          

它是由一个一个结点结合在一起的,而每个节点的地址没有关联,都是随机的,东一个西一个。

注意单链表的最后一个结点指向NULL;

这里我们简单的定义一个单链表:

                                        

如何去访问链表里的各个结点呢:我们定义一个cur指针指向单链表的头节点,然后通过cur->next来依次访问剩下的结点,如果cur==NULL,说明链表访问完了。

接下来我们来实现单链表几个基本的功能:(完整源码结尾附上)

单链表的尾插

                           

这里需要额外注意:因为phead是plist的临时拷贝,使用要注意二级指针与一级指针的使用,当然不使用二级指针也是可以 ,也有不同的写法,这里这样写是让大家小心注意一下其中的大坑。

单链表的头插:

 

单链表的尾删:

                                        

                                                       

需要注意,这里需要区分一个结点多个结点的不同情况。

单链表的头删:

                            

这里需要注意:我们的思路是把头节点的指向原本头结点的下一个结点,然后还需要free掉原本的头节点,这里会出现一种很常见的错误

                             

先free掉tmp后,就没办法再进行头结点的链接,丢失了地址。

正确的写法:(大家注意区分)

                                

关于链表还有一个知识点,就是哨兵位

                           

哨兵位就是在头节点的前面给它再加一个结点,让它充当头节点,但它不存储具体的数据,只是更方便进行头插头删等操作,哨兵位的含金量不是很高,有时候方便用来刷OJ题,这里大家简单了解下,有兴趣的朋友自己敲敲代码试一下把。

单链表我们就学到这里,接下来我们学习一下双链表

这里我们先再来了解下链表有哪些分类:

        

我们注意到,单链表和双链表的区别是:单链表一个结点只能指向下一个结点,而双边表一个结点可以指向前一个结点也可以指向后一个结点。

循环链表的特殊之处则是尾结点直接指向了头节点,实现了一个循环。

接下来我们先来比较一下俩种链表:

我们主要学习一下带头双向循环链表: 

根据上面单链表的学习,我们简单写几个功能让加强大家对双向链表的理解:

带头双向循环链表的尾插:

                         

我们可以明显感受到,双向循环链表的数据处理只需要几个简单的指向就可以简单完成;

带头双向循环链表的尾删:

                                 

知识的分享就结束了,接下来附上顺序表与链表的源码:(因为内容较多,所以不是很全,但有具体的框架,大家可以根据自己的需要进行补全)

顺序表:

SeqList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.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 SLCheckCapacity(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);

 SeqList.c

#include"SeqList.h"

void SLInit(SL* psl)
{
	assert(psl);

	psl->a = NULL;
	psl->size = 0;
	psl->capacity = 0;
}

void SLDestroy(SL* psl)
{
	assert(psl);

	if (psl->a != NULL)
	{
		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)
	{
		int newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType)*newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		psl->a = tmp;
		psl->capacity = newCapacity;
	}
}

// 10:37
void SLPushBack(SL* psl, SLDataType x)
{
	assert(psl);

	SLCheckCapacity(psl);

	psl->a[psl->size] = x;
	psl->size++;
}

void SLPushFront(SL* psl, SLDataType x)
{
	assert(psl);

	SLCheckCapacity(psl);

	// 挪动数据
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->a[end + 1] = psl->a[end];
		--end;
	}

	psl->a[0] = x;
	psl->size++;
}

void SLPopBack(SL* psl)
{
	assert(psl);

	// 空
	// 温柔的检查
	/*if (psl->size == 0)
	{
		return;
	}*/

	// 暴力检查
	assert(psl->size > 0);

	//psl->a[psl->size - 1] = -1;
	psl->size--;
}

// 10:47
void SLPopFront(SL* psl)
{
	assert(psl);

	// 暴力检查
	assert(psl->size > 0);

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

	psl->size--;
}

// 注意pos是下标
// size是数据个数,看做下标的话,他是最后一个数据的下一个位置
void SLInsert(SL* psl, int pos, SLDataType x)
{
	assert(psl);
	assert(pos >= 0 && pos <= psl->size);

	SLCheckCapacity(psl);

	// 挪动数据
	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);

	// 挪动覆盖
	int begin = pos + 1;
	while (begin < psl->size)
	{
		psl->a[begin - 1] = psl->a[begin];
		++begin;
	}

	psl->size--;
}

Test.c:

#include<stdio.h>
#include"SeqList.h"


void TestSL1()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPushBack(&sl, 5);
	SLPushBack(&sl, 6);
	SLPushBack(&sl, 7);
	SLPushBack(&sl, 8);
	SLPushBack(&sl, 9);
	SLPrint(&sl);

	SLPushFront(&sl, 10);
	SLPushFront(&sl, 20);
	SLPushFront(&sl, 30);
	SLPushFront(&sl, 40);
	SLPrint(&sl);

	SLDestroy(&sl);
}

void TestSL2()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPushBack(&sl, 5);
	SLPrint(&sl);

	SLPopBack(&sl);
	SLPrint(&sl);

	SLPopBack(&sl);
	SLPopBack(&sl);
	SLPopBack(&sl);
	SLPopBack(&sl);
	SLPrint(&sl);



	SLPushFront(&sl, 10);
	SLPushFront(&sl, 20);
	SLPushFront(&sl, 30);
	SLPushFront(&sl, 40);
	SLPrint(&sl);

	SLDestroy(&sl);
}

void TestSL3()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPushBack(&sl, 5);
	SLPrint(&sl);

	SLPopFront(&sl);
	SLPrint(&sl);

	SLPopFront(&sl);
	SLPrint(&sl);

	SLPopFront(&sl);
	SLPrint(&sl);

	SLPopFront(&sl);
	SLPrint(&sl);

	SLPopFront(&sl);
	SLPrint(&sl);

	//SLPopFront(&sl);
	//SLPrint(&sl);
}

void TestSL4()
{
	

	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPushBack(&sl, 5);
	SLPrint(&sl);

	SLInsert(&sl, 2, 20);
	SLPrint(&sl);

	SLInsert(&sl, 6, 20);
	SLPrint(&sl);

	SLInsert(&sl, 0, 20);
	SLPrint(&sl);

	SLInsert(&sl, 10, 20);
	SLPrint(&sl);

	SLDestroy(&sl);
}

void TestSL5()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPushBack(&sl, 5);
	SLPrint(&sl);

	SLErase(&sl, 3);
	SLPrint(&sl);

	SLErase(&sl, 3);
	SLPrint(&sl);

	//SLErase(&sl, 3);
	//SLPrint(&sl);
}

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

单链表: 

 SList.h

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

typedef int SLNDataType;

// Single List
typedef struct SListNode
{
	SLNDataType val;
	struct SListNode* next;
}SLNode;

void SLTPrint(SLNode* phead);
void SLTPushBack(SLNode** pphead, SLNDataType x);
void SLTPushFront(SLNode** pphead, SLNDataType x);

void SLTPopBack(SLNode** pphead);
void SLTPopFront(SLNode** pphead);

SLNode* SLTFind(SLNode* phead, SLNDataType x);

// posǰ
SLNode* SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);

// ɾposλ
void SLTErase(SLNode** pphead, SLNode* pos);
void SLTDestroy(SLNode** pphead);

SList.c

#include"SList.h"

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

SLNode* CreateNode(SLNDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;
	return newnode;
}

void SLTPushBack(SLNode** pphead, SLNDataType x)
{
	SLNode* newnode = CreateNode(x);

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		// 找尾
		SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		tail->next = newnode;
	}	
}

void SLTPushFront(SLNode** pphead, SLNDataType x)
{
	SLNode* newnode = CreateNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

void SLTPopBack(SLNode** pphead)
{
	// 温柔的检查
	//if (*pphead == NULL)
	//	return;

	// 空
	assert(*pphead);

	// 1、一个节点
	// 2、一个以上节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		// 找尾
		/*SLNode* prev = NULL;
		SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;

		prev->next = NULL;*/

		SLNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}

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

void SLTPopFront(SLNode** pphead)
{

	assert(*pphead);

	
	SLNode* tmp = *pphead;
	free(tmp);
	*pphead = (*pphead)->next;
}

Test.c

#include"SList.h"

void TestSLT1()
{
	SLNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

	//SLTPopBack(&plist);
	//SLTPrint(plist);
}

void TestSLT2()
{
	SLNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);


	//SLNode* pos = SLTFind(plist, 3);
	//SLTInsert(&plist, pos, 30);
}

//int main()
//{
//	TestSLT2();
//
//	return 0;
//}

struct ListNode
{
	struct ListNode* next;
	int val;
};

struct ListNode* removeElements(struct ListNode* head, int val)
{
	struct ListNode* prev = NULL;
	struct ListNode* cur = head;

	//while(cur != NULL)
	while (cur)
	{
		if (cur->val == val)
		{
			struct ListNode* next = cur->next;
			free(cur);

			if (prev)
				prev->next = next;

			cur = next;
		}
		else
		{
			prev = cur;
			cur = cur->next;
		}
	}

	return head;
}

int main()
{
	struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
	struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));
	struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));
	struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));
	n1->val = 7;
	n2->val = 7;
	n3->val = 7;
	n4->val = 7;

	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = NULL;

	struct ListNode* head = removeElements(n1, 7);

	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值