数据结构--顺序表,链表,双向链表

数据结构的学习,从浅到深的学习,在了解他们的概念后,当然要明白他们分别是怎么实现的,同时通过对比来了解他们的异同点。

内存中管理数据--数据结构

硬盘中管理数据--数据库

一.数据结构

1.1 什么是数据结构

所谓数据结构,拆开来讲,就是数据和结构。

数据:所有能输入到计算机中并且能被程序识别和处理的符号集合,包括数值数据(整数,实数),非数值型数据(图形,声音,文字)。

结构:数据之间的关系

数据对象:相同特性的数据元素的集合

数据元素:数据的基本单位

数据项:数据元素的最小单位

关系图:

最基础的数据结构:数组

1.2 数据结构三要素

1.2.1逻辑结构

集合:数据和元素之间没有关系

线性结构:数据和元素之间一对一

树结构:数据和元素之间一对多

图结构:数据和元素之间多对多

1.2.2 物理结构(存储结构)

顺序存储结构

链接存储结构

1.2.3 数据的运算

二.线性表

我们先引入一个概念:线性表。线性表是n个具有相同特性的数据元素的有限序列

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

1. 顺序表

顺序表的底层结构是数组

1.1顺序表的实现

1.1.1静态顺序表和动态顺序表
静态顺序表
//给定的数组长度,如果不够,会导致后续的数据保存失败
#define N 100//长度通过宏的方式进行优化  使用100太过于麻烦,通过N代替100   
typedef int SLDataType;//存储的也不一定是整型   typedef a b;就是用b代替a//
//struct SeqList
//{
	//int a[100];//固定的长度    不代表是一个有效的数据   
	//int a[N];//优化上一行
	//SLDataType a[N];//优化上一行
	//int size;//有效的数据的个数
//};

 

动态顺序表
struct SeqList
{
	SLDataType* arr;//存储数据的底层结构
	int capacity;//记录顺序表的空间大小  即申请到的实际空间大小  有空间不一定有实际数据
	int size;//记录顺序表当前有效的数据个数
	//capacity是最大空间,size是已经使用了的空间
};

静态顺序表就是给定长度的数组int arr[10]

动态顺序表就是动态设置数组长度int *arr

1.1.2 初始化和销毁

这里的SL* ps是数组指针

头文件SeqList.h中定义

void SLInit(SL* ps);

 SeqList.c的代码

//初始化和销毁
void SLInit(SL* ps)
{
    assert(ps);
    ps->a = (SLDataType*)malloc(N*sizeof(SLDataType));
    if(ps->a == NULL)
    {
     perror("malloc fail");
     return;
    }
	
	ps->size = ps->capacity = 0;//ps.size = ps`.capacity = 0;
}

复杂度分析:

时间复杂度:由于没有其他未知数,所以所需时间是常数O(1)

空间复杂度:动态开辟N个空间 O(N)

void SLDestroy(SL* ps)
{
	assert(ps);
	if (ps->arr)
	{
		free(ps->arr);
	}
    ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

 初始化,将结构体里面的数组置为NULL,值变量(有效数据个数,空间大小)置为0

1.1.3打印

头文件SeqList.h中定义

void SLPrint(SL* ps);//打印  *ps是为了保持接口一致性

SeqList.c的代码

//void SLPrint(SL * ps)//顺序表的打印
//{
   assert(ps);
   if(ps->size == 0)
   {
    printf("顺序表为空\n");
    return;
   }
//	for (int i = 0; i < ps->size; i++)
//	{
//		printf("%d ", ps->arr[i]);
//	}
//	printf("\n");
//
//}

复杂度分析:

时间复杂度:打印顺序表中的所有数据 O(N)

空间复杂度:打印顺序表不需要开辟额外空间   O(1)

1.1.4扩容.

头文件SeqList.h中定义

//扩容
//void SLCheckCapacity(SL* ps)

 SeqList.c的代码

//void SLCheckCapacity(SL* ps)//扩容判断
//{
//	if (ps->size == ps->capacity)
//	{
//		//ps->arr = (SLDataType*)realloc(ps->arr, 2 * ps->capacity);有问题 在上面capacity已经赋值为0
//		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//创建一个新的变量newcapacity
//		//如果capacity是0,那么值是4 4是比特的意思		
//       //ps->arr = (SLDataType*)realloc(ps->arr, newcapacity);
//		SLDataType* tmp = ps->arr = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));
//		//临时指针指向开辟的空间
//		if (tmp == NULL)
//		{
//			perror("realloc fail!");
//			exit(1);//扩容失败直接中断程序
//		}
//		//扩容成功
//		//free(ps->arr);//此时arr里面已经有了空间,要先释放
//		//不对,这是realloc做的事情
//		ps->arr = tmp;
//		ps->capacity = newcapacity;//此时容量已经变了,变成了newcapacity
//	}
//}

1.1.5头插/尾插

头文件SeqList.h中定义

//顺序表的头部/尾部插入
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);

SeqList.c的代码

//顺序表的头部/尾部插入
void SLPushBack(SL* ps, SLDataType x) {
	//断言--粗暴的解决方式
	//assert(ps != NULL);
	assert(ps);

	//if判断--温柔的解决方式
	//if (ps == NULL) {
	//	return;
	//}

	//空间不够,扩容
	SLCheckCapacity(ps);

	//空间足够,直接插入
	ps->arr[ps->size++] = x;
	//ps->size++;
}

复杂度分析:

时间复杂度:没有变量O(1)

空间复杂度:以最坏的情况,扩容O(N)

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

	//判断是否扩容
	SLCheckCapacity(ps);

	//旧数据往后挪动一位
	for (int i = ps->size; i > 0; i--) //i = 1
	{
		ps->arr[i] = ps->arr[i - 1]; //ps->arr[1] = ps->arr[0]
	}
	ps->arr[0] = x;
	ps->size++;
}

复杂度分析:

时间复杂度:从头部插入数据,后面数据要依次覆盖O(N)

空间复杂度:最坏情况,扩容O(N)

1.1.6头删/尾删

头文件SeqList.h中定义

//顺序表的头部/尾部删除
void SLPopBack(SL* ps);
void SLPopFront(SL* ps);

SeqList.c的代码

//顺序表的头部/尾部删除
void SLPopBack(SL* ps) {
	assert(ps);
	assert(ps->size);

	//顺序表不为空
	//ps->arr[ps->size - 1] = -1;
	ps->size--;
}

复杂度分析:

时间复杂度:往前覆盖数据O(N)

空间复杂度:没有开辟其他空间         O(1)                                                                                                                                                                                                                                                                         

void SLPopFront(SL* ps) {
	assert(ps);
	assert(ps->size);

	//不为空执行挪动操作
	for (int i = 0; i < ps->size-1 ;i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

复杂度分析:

时间复杂度:没有额外时间的消耗,O(1)

空间复杂度:没有额外空间的消耗,O(1)

1.1.7在指定位置之前插入数据/删除指定位置数据

头文件SeqList.h中定义

/指定位置之前插入数据
//删除指定位置数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);

SeqList.c的代码

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

	//pos及之后的数据往后挪动一位,pos空出来
	for (int i = ps->size; i > pos ;i--)
	{
		ps->arr[i] = ps->arr[i - 1]; //ps->arr[pos+1] = ps->arr[pos]
	}
	ps->arr[pos] = x;
	ps->size++;
}

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

	//pos以后的数据往前挪动一位
	for (int i = pos;i < ps->size-1;i++)
	{
		ps->arr[i] = ps->arr[i + 1];//ps->arr[i-2] = ps->arr[i-1];
	}
	ps->size--;
}

1.1.8在顺序表中查找x

头文件SeqList.h中定义

//查找
int SLFind(SL* ps, SLDataType x);

SeqList.c的代码

在顺序表中查找x
//int SLFind(SL* ps, SLDataType x)
//{
//	for (int i = 0; i < ps->size; i ++)
//	{
//		if (ps->arr[i] == x)
//		{
//			return i;
//		}
//	}
//	return -1;
//}

复杂度分析:

时间复杂度:最坏需要遍历,O(N)

空间复杂度:没有额外空间消耗,O(1)

2. 链表

链表是由一个一个节点组成的

一个节点包括数据和指针

2.1单向链表

typedef int SLTDataType;
//链表由节点组成
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
} SLTNode;

2.2.1头插/尾插
//尾插
SLTNode* SLTBuyNode(SLTDataType x)
{
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    newnode->data = x;
    newnode->next = NULL;

    return newnode;
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);//*pphead是指向第一个节点的地址
    //SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    //newnode->data = x;
    //newnode->next = NULL;
//    //建立新节点
    SLTNode* newnode = SLTBuyNode(x);
//    //链表为空,新节点作为phead
    if (*pphead == NULL)
    {
        *pphead = newnode;
        return;
    }
//    //链表不为空,找尾节点
//    //遍历链表,建立一个临时指针

    SLTNode* ptail = *pphead;
    while (ptail->next)
    {
        ptail = ptail->next;
    }
    //跳出循环,说明此时的ptail就是尾节点
    ptail->next = newnode;
}

复杂度分析:

时间复杂度:O(N)

空间复杂度:O(1)

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);
    SLTNode* newnode = SLTBuyNode(x);

    //newnode *pphead
    newnode->next = *pphead;
    *pphead = newnode;
}

单链表的头插传参需要 二级指针。因为头插数据需要改变一级指针的指向,形参的改变无法影响实参,所以需要二级指针

复杂度分析:

时间复杂度:O(1)

空间复杂度:O(1)

2.2.2头删/尾删
//尾删
void SLTPopBack(SLTNode** pphead)
{
    assert(pphead);
    assert(*pphead);//链表不能为空

    //链表只有一个节点,有多个节点
    if ((*pphead)->next == NULL)
    {
        free(*pphead);
        *pphead = NULL;
        return;
    }
    SLTNode* ptail = *pphead;
    SLTNode* prev = NULL;
    while (ptail->next)
    {
        prev = ptail;
        ptail = ptail->next;
    }
    prev->next = NULL;
    //销毁尾节点
    free(ptail);
    ptail = NULL;
}

复杂度分析:

时间复杂度:O(N)

空间复杂度:O(1)

/头删
void SLTPopFront(SLTNode** pphead)
{
    assert(pphead);
    //链表不能为空
    assert(*pphead);

    //让第二个节点成为新的头
    //把旧的头节点释放掉
    SLTNode* next = (*pphead)->next;
    free(*pphead);
    *pphead = next;
}

复杂度分析:

时间复杂度:O(1)

空间复杂度:O(1)

2.2.3查找
//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);

    //遍历链表
    SLTNode* pcur = *pphead;
    while (pcur)//等价于pcur != NULL
    {
        if (pcur->data == x)
        {
            return pcur;
        }
        pcur = pcur->next;
    }
    //没有找到
    return NULL;
}

复杂度分析:

时间复杂度:O(N)

空间复杂度:O(1)

2.2.4在指定位置之前插入数据/在指定位置之后插入数据
//在指定位置之前插入数据
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
    assert(pphead);
    assert(pos);
    //要加上链表不能为空  pos不能为空,而pos在链表里面,因此链表也不能为空
    assert(*pphead);
    //pos刚好是头节点
    if (pos == *pphead)
    {
        //头插
        SLTPushFront(pphead, x);
        return;
    }
    //pos不是头节点
    SLTNode* prev = *pphead;//建立一个临时地址
    SLTNode* newnode = SLTBuyNode(x);//申请一个新节点
    while (prev->next != pos)
    {
        prev = prev->next;
    }
    //prev->next->newnode
    prev->next = newnode;
    newnode->next = pos;

}

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
    assert(pos);

    SLTNode* newnode = SLTBuyNode(x);//申请新节点

    //pos newnode pos->next
    newnode->next = pos->next;
    pos->next = newnode;
}

2.2.5删除pos节点/删除pos之后的节点
//删除指定位置的节点pos
void SLTEraseBefore(SLTNode** pphead, SLTNode* pos)
{
    assert(pphead);
    assert(*pphead);
    assert(pos);
    //pos刚好是头节点,没有前驱节点,执行头删
    if (*pphead == pos)
    {
        //头删
        SLTPopFront(pphead);
        return;
    }
        
    SLTNode* prev = *pphead;
    while (prev->next != pos)
    {
        prev = prev->next;
    }
    //prev pos pos->next
    prev->next = pos->next;
    free(pos);
    pos = NULL;


}

/删除指定位置之后的节点pos
void SLTEraseAfter(SLTNode* pos)
{
    assert(pos);
    assert(pos->next);
    //pos  pos->next  pos->next->next
    SLTNode* del = pos->next;

    pos->next = pos->next->next;
    free(del);
    del = NULL;
}

2.2.6打印/销毁链表
void SLTPrint(SLTNode* phead) 
{
    SLTNode* pcur = phead;//pcur是一个临时指针
    while (pcur)
    {
        printf("%d->", pcur->data);
        pcur = pcur->next;
    }
    printf("NULL\n");

}

复杂度分析:

时间复杂度:O(N)

空间复杂度:O(1)

//销毁链表
void SlistDesTroy(SLTNode** pphead)
{
    assert(pphead);
    assert(*pphead);

    SLTNode* pcur = *pphead;
    while (pcur)
    {
        SLTNode* next = pcur->next;
        free(pcur);
        pcur = next;
    }
    *pphead = NULL;
}

复杂度分析:

时间复杂度:O(N)

空间复杂度:O(1)

2.2 双向链表

//定义双向链表中节点的结构
typedef int LTDataType;
typedef struct ListNode
{
	int data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

带头双向循环链表

2.2.1头插/尾插
//尾插
void LTPushBack(LTNode* phead, LTDataType x) {
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	//phead phead->prev(ptail)  newnode
	newnode->next = phead;
	newnode->prev = phead->prev;

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

复杂度分析:

时间复杂度:O(1)

空间复杂度:O(1)

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

	LTNode* newnode = LTBuyNode(x);
	//phead newnode phead->next
	newnode->next = phead->next;
	newnode->prev = phead;

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

复杂度分析:

时间复杂度:O(1)

空间复杂度:O(1)

void LTPrint(LTNode* phead) {
	//phead不能为空
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

2.2.2头删/尾删
//尾删
void LTPopBack(LTNode* phead) {
	assert(phead);
	//链表为空:只有一个哨兵位节点
	assert(phead->next != phead);

	LTNode* del = phead->prev;
	LTNode* prev = del->prev;

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

	free(del);
	del = NULL;
}

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

	LTNode* del = phead->next;
	LTNode* next = del->next;

	//phead del next
	next->prev = phead;
	phead->next = next;

	free(del);
	del = NULL;
}
LTNode* LTFind(LTNode* phead, LTDataType x) {
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x) {
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

2.2.3查找
LTNode* LTFind(LTNode* phead, LTDataType x) {
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x) {
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

2.2.4在指定位置之前插入数据/在指定位置之后插入数据

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x) {
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	//pos newnode pos->next
	newnode->next = pos->next;
	newnode->prev = pos;

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

2.2.5删除pos节点/删除pos之后的节点
//删除pos位置的数据
void LTErase(LTNode* pos) {
	assert(pos);

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

	free(pos);
	pos = NULL;
}

2.2.6打印/销毁链表
void LTDesTroy(LTNode* phead) {
	//哨兵位不能为空
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//链表中只有一个哨兵位
	free(phead);
	phead = NULL;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值