【数据结构中的线性表】

从前从前有只猫头鹰牠站在屋顶...............................................................................................

目录

前言

一、【线性表的介绍】

二、【顺序表】

2.1【顺序表的介绍】

2.2【顺序表的实现】

2.3【顺序表的问题和缺点】

2.4【顺序标练习题目】

三、【单链表】

3.1【单链表的介绍】

3.2【单链表的实现】

3.3【单链表的问题和缺点】

3.4【单链表练习题目】

四、【双链表】

4.1【带头双链表的介绍】

4.2【带头双向链表的实现】

4.2【问题和缺点】

4.3【顺序表和链表的区别】

五、【链表的一些经典试题】

总结


前言

在编写本篇博客时,学校正要进行期末考试,花费的时间比较长,内容很多,还望耐心看完!


一、【线性表的介绍】

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储

二、【顺序表】

2.1【顺序表的介绍】

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

主要包括以下几种:
1. 静态顺序表:使用定长数组存储元素。

2. 动态顺序表:使用动态开辟的数组存储。

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

这里只是实现顺序表的增删查改功能,为了方便调试,就不实现菜单选择的功能了。

2.2【顺序表的实现】

这里也是采用了,模块化编程的方式,在代码里会有实现的思路:

SeqList.h:

#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define INICAPA 4//定义顺序表的初始容量,以便存储数据

typedef int SeDatatype;//这里把int 类型重命名为SeDatatype 是为了让顺序表中的数据类型能够可变。
typedef struct SeqList//定义顺序表
{
	SeDatatype* pss;//定义顺序表的起始空间地址,方便后续采用malloc函数来开辟顺序表的空间。
	int capacity;//顺序表的容量
	int sz;//顺序表中有效信息的总数
}SeqList;
void SeInit(SeqList* psl);//顺序表的初始化函数。
void SePushBack(SeqList* psl,SeDatatype x);//顺序表实现尾插功能的函数
void SePushFront(SeqList* PSL, SeDatatype x);
void SeDstory(SeqList* psl);//销毁顺序表的函数,这一步主要是把顺序表开辟的空间进行释放,从而完成销毁顺序表的操作。
void SePrint(SeqList* psl);//实现对顺序表的元素进行打印的功能。
void SePopback(SeqList* psl);
void SePopfront(SeqList* psl);
void SeFind(SeqList* psl, SeDatatype x);
void SeInsert(SeqList* psl, int pos, SeDatatype x);
void SeErase(SeqList* psl, int pos);
void SeModify(SeqList* psl, int pos, SeDatatype x);

SeqList.c:
 

#define _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"
void SeInit(SeqList* psl)
{
	assert(psl);//检查顺序表的地址是否为空
	psl->pss =(SeDatatype*) malloc(sizeof(SeDatatype) * INICAPA);//通过顺序表起始空间地址为顺序表开辟空间。
	if (psl->pss == NULL)
	{
		perror("malloc failed");
		return;
	}
	psl->capacity = INICAPA;//将初始容量设置为4
	psl->sz = 0;//由于没有插入数据,此时顺序表中的有效信息个数也为0.
}
void SeDstory(SeqList* psl)
{
	free(psl->pss);//首先释放掉顺序表为存放数据所开辟的空间。
	psl->pss = NULL;
	psl->capacity = 0;//顺序表被销毁,容量变为0
	psl->sz = 0;//顺序表中没有元素,有效信息的个数也为0.
}
void SePrint(SeqList* psl)//对顺序表的元素进行打印。
{
	for (int i = 0; i < psl->sz; i++)
	{
		printf("%d ", psl->pss[i]);//遍历顺序表所有元素并打印。
	}
	printf("\n");
}
void checkcapa(SeqList* psl)//检查顺序表的容量,以方便扩容。
{
	assert(psl);
	if (psl->capacity == psl->sz)//如果有效信息的个数与顺序表的容量相等,说明需要扩容。
	{
		SeDatatype* tmp = (SeDatatype*)realloc(psl->pss, sizeof(SeDatatype) * psl->capacity * 2);//采用realloc进行扩容。
		if (tmp == NULL)
		{
			perror("realloc failed");
			return;
		}
		
		psl->pss = tmp;
		psl->capacity *=2;//容量变为之前的2倍
	}
}
void SePushBack(SeqList* psl,SeDatatype x)//顺序表的地址和所需要插入的数据。
{
	assert(psl);
	checkcapa(psl);//每次在进行插入操作之前,应先检查一遍容量,防止容量不够。
	psl->pss[psl->sz] = x;//在顺序表的末尾进行插入。
	psl->sz++;//插入完成后,顺序表的有效信息个数+1
}
void SePushFront(SeqList* psl, SeDatatype x)//顺序表的地址和所需要插入的数据。
{
	assert(psl);
	checkcapa(psl);//每次在进行插入操作之前,应先检查一遍容量,防止容量不够。
	int end = psl->sz-1;//头插之前需要先把顺序表中的元素向后进行挪动。
	while (end >= 0)
	{
		psl->pss[end + 1] = psl->pss[end];//挪动
		end--;
	}
	psl->pss[0] = x;//在顺序表的首元素位置,放入需要插入的元素。
	psl->sz++;//插入完成后,顺序表的有效信息个数+1
}
void SePopback(SeqList* psl)//尾删,只需要传顺序表的地址进行操作即可。
{
	assert(psl->sz > 0);//在删之前应判断是否为空顺序表,也就是判断顺序表里是否有元素,如果没有就停止删除的操作。
	assert(psl);
	psl->pss[psl->sz - 1] = 0;//只需要把最后一个元素置为0,并把顺序表总大小-1即可。
	/*free(psl->pss+psl->sz - 1);
	psl->pss+psl->sz - 1 = NULL;*///这里是不能这样写的,因为,顺序表申请的空间是连续的,不能单独对某块空间进行释放。
	psl->sz--;
}
void SePopfront(SeqList* psl)//头插
{
	assert(psl);
	assert(psl->sz > 0);在删之前应判断是否为空顺序表,也就是判断顺序表里是否有元素,如果没有就停止删除的操作。
	int start = 0;
	while (start < psl->sz-1)
	{
		psl->pss[start] = psl->pss[start + 1];//将所有元素向前挪动一个,从而将第1个元素进行覆盖.
		start++;
	}
	psl->sz--;//并把顺序表总大小-1即可。
}
void SeFind(SeqList* psl, SeDatatype x)//查找
{
	for (int i = 0; i < psl->sz; i++)//遍历顺序表,查找顺序标中是否有需要的元素。
	{
		if (psl->pss[i] == x)
		{
			return i;//有的话就返回该元素的下标。
		}
	}
	return -1;//没有就返回-1。
}
void SeInsert(SeqList* psl, int pos, SeDatatype x)//插入函数,pos代表需要插入位置的下标。
{
	assert(psl);
	assert(pos >= 0 && pos <= psl->sz);//检查插入的位置是否合法,也就是,是否在0-sz的范围内。
	int end = psl->sz-1;
	while (end >= pos)
	{
		psl->pss[end + 1] = psl->pss[end];//将pos及其之后的元素向后挪动1位
		end--;
	}
	psl->pss[pos] = x;//再把需要插入的数据进行插入。
	psl->sz++;
}
void SeErase(SeqList* psl, int pos)//删除函数,pos代表需要插入位置的下标。
{
	assert(psl);
	assert(pos >= 0 && pos <= psl->sz);//检查插入的位置是否合法,也就是,是否在0-sz的范围内。
	int start = pos;
	while (start < psl->sz - 1)
	{
		psl->pss[start] = psl->pss[start + 1];//将pos及其之后的元素向前挪动1位,将其覆盖。
		start++;
	}
	psl->sz--;
}
void SeModify(SeqList* psl, int pos, SeDatatype x)//修改函数,找到之后进行修改
{
	assert(psl);
	assert(pos >= 0 && pos <= psl->sz);
	psl->pss[pos] = x;
}

2.3【顺序表的问题和缺点】

顺序表中存在的问题:

1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

4.申请的空间为连续的,进行操作时需要挪动大量的数据。

顺序表的缺点:

  1. 在进行插入和删除操作时,可能涉及到数据的搬移操作,因为顺序表中元素的存储是连续的,删除和插入操作可能需要将元素后移或前移。

  2. 当顺序表的大小不够时,需要进行扩容操作。扩容操作需要重新分配内存,并将原有数据拷贝到新的内存中,这个过程需要消耗一定的时间和空间。

  3. 顺序表的删除操作一般只能删除最后一个元素,如果要删除其他位置的元素需要先进行后续元素的前移操作,效率较低。而链表则没有这个问题。

2.4【顺序表练习题目】

题目1:

链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

分析与解答:

双下标遍历顺序表

int removeElement(int* nums, int numsSize, int val)
{
    int src=0;
    int dst=0;
    while(src<numsSize)
    {
        if(nums[src]!=val)//nums[src]不等于val就赋值给nums[dst]
        {
            nums[dst++]=nums[src++];
        }
        else//等于val就跳过
        {
            src++;
        }
    }
    return dst;
}

题目2:

链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

分析与解答:

快慢下标遍历顺序表

int removeDuplicates(int* nums, int numsSize) {
    int dst=0;
    int src=1;
    while(src<numsSize)
    {
        if(nums[src]==nums[dst])//判断nums[dst]与nums[src]是否相等,相等就跳过,直到遇到第一个 
                                //跟他不相等的
        {
            src++;
        }
        else//遇到不相等的以后,先让dst++,到下一个位置,在将其赋值为第一个不相等的。
        {
            dst++;
            nums[dst]=nums[src];

        }
    }
    return dst+1;//最后返回dst+1
}

题目3:

链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

分析与解答:

1.暴力解答,先合并再排序

void bubble(int* nums1,int nums1Size)
{
    
    for(int i=1;i<=nums1Size-1;i++)
    {   
        for(int j=0;j<nums1Size-i;j++)
        {     
            if(nums1[j]>nums1[j+1])
            {    
                int temp=nums1[j];
                nums1[j]=nums1[j+1];
                nums1[j+1]=temp;
            }
        }
    }
}
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) 
{
    memmove(nums1+m,nums2,sizeof(int)*n);//利用memmove进行合并

    bubble(nums1,nums1Size);    //利用冒泡排序进行排序
    
}

2.依次比较,取大的值进行尾插。

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) 
{
      int src=m-1;//从顺序表的最后一个元素开始比较,因为最后一个元素是最大的
      int dst=n-1;
      int add=nums1Size-1;//定义add控制尾插
      while(dst>=0 && src>=0)//有一个顺序标被遍历完,循环停止
      {
          if(nums1[src]>nums2[dst])//依次进行比较
          {
              nums1[add]=nums1[src];//较大的尾插
              src--;
              add--;
          }
          else 
          {
              nums1[add]=nums2[dst];
              dst--;
              add--;

          }
      }
      while(dst>=0)//特殊情况,比如nums1[3,3,3,3],nums2[2,4,7]
      {            //最后会出现nums1遍历完了,nums2没有遍历完,只需将剩余的元素进行尾插即可。
                   //nums1[0],nums2[1],m+n=1,m=0,n=1这种情况也适合。
          nums1[add]=nums2[dst];
          dst--;
          add--;
      }
}

三、【单链表】

3.1【单链表的介绍】

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

链表主要有以下几种结构:

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

3.2【单链表的实现】

SLIst.h部分:

#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDataType;
typedef struct SList//定义单链表
{
	SLDataType data;//定义data用来存放单链表里的数据
	struct SList* next;//定义的是一个单链表的指针,用来指向下一个节点,他也是一个单链表类型,因为它里面存放的也是自己的数据和它下一个节点的指针.
}SLT;
void SLTPrint(SLT* phead);//打印链表的各个元素。
void SLTPushFront(SLT** pphead, SLDataType x);//头插
void SLTPushBack(SLT** pphead, SLDataType X);//尾插
void SLTPopFront(SLT** pphead);//头删
void SLTPopBack(SLT** pphead);//尾删
SLT*  SLFind(SLT* phead, SLDataType x);//查找
void SLTModify(SLT* phead, SLDataType x,SLDataType newdata);//修改
void SLTInsertFront(SLT** pphead,SLT* pos, SLDataType x);//任意位置之前的插入
void SLTInsertAfter(SLT** pphead, SLT* pos, SLDataType x);//任意位置之后的插入
void SLTErace(SLT** pphead, SLT* pos);//任意位置的删除
void SLTEraceAfter(SLT* pos);//任意位置之后的删除

SLIst.c部分:

#define _CRT_SECURE_NO_WARNINGS
#include "SList.h"
void SLTPrint(SLT* phead)//打印
{
	SLT* cur = phead;//定义一个指针,从链表的头节点开始往后遍历
	while (cur != NULL)
	{
		printf("%d->", cur->data);//打印各个节点的数据。
		cur = cur->next;
	}//只要没遍历完单链表就不跳出循环,这里没写成
	//while(cur->next!=NULL)的原因是因为,需要把单链表遍历完全,如果这样写只会遍历到最后一个节点,而不会遍历完全,最后一个节点的值也不会进行打印。
	printf("NULL\n");//遍历完单链表在末尾打上NULL
}
SLT* CreatNewnode(SLDataType x)
{
	SLT* pnew = (SLT*)malloc(sizeof(SLT));
	if (pnew == NULL)
	{
		perror("malloc failed");
		return NULL;
	}
	pnew->data = x;
	pnew->next = NULL;
	return pnew;
}
void SLTPushFront(SLT** pphead, SLDataType x)//头插,注意这里传的是,单链表的地址。
{
	SLT* pnew = CreatNewnode(x);//在插入之前,定义一块新的空间来存放新的数据。
	pnew->next = *pphead;//首先将新创建的节点与之前的头节点进行连接
	/**pphead = pnew;*/
	*pphead = pnew;//之后再把头节点变为新创建的节点
	/*(*pphead)->next = NULL;*/

}
void SLTPushBack(SLT** pphead, SLDataType x)//尾插
{
	assert(pphead);
	SLT* pnew=CreatNewnode(x);在插入之前,定义一块新的空间来存放新的数据。
	if (*pphead==NULL)//如果是空链表,就把头节点变为新创建的这个节点
	{
		*pphead = pnew;
	}
	else
	{
		SLT* tail = *pphead;//如果不是空链表,定义tail,向后遍历节点
		while (tail->next != NULL)//直到遍历到最后一个节点
		{
			tail = tail->next;
		}
		tail->next = pnew;//将最后一个节点与新创建的节点进行连接。
	}
}
void SLTPopFront(SLT** pphead)//头删
{
	assert(pphead);
	assert(*pphead);
	SLT* del = *pphead;//定义del将头结点赋值给del
	*pphead = (*pphead)->next;//再将头节点变为头结点的下一个节点
	free(del);//释放之前的头节点
	del = NULL;//并置空
}
void SLTPopBack(SLT** pphead)//尾删
{
	if ((*pphead)->next == NULL)//如果链表为空,就把第1个节点置成空即可
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//这种方法是找到最后一个节点的同时,将最后一个节点的前一个节点变为尾节点
		//SLT* prev = NULL;//定义Prev用来记录最后一个节点的前一个节点
		//SLT* tail = *pphead;//从头遍历找尾节点
		 找尾
		//while (tail->next)
		//{
		//	prev = tail;
		//	tail = tail->next;
		//}

		//free(tail);
		//prev->next = NULL;
		SLT* tail = *pphead;
		while (tail->next->next != NULL)//用两个next使tail停在了尾节点的前一个节点上
		{
			tail = tail->next;
		}
		free(tail->next);//此时,将最后一个节点释放即可
		tail->next = NULL;
	}
}
SLT*  SLFind(SLT* phead, SLDataType x)//查找就是通过遍历链表所有的元素
{
	SLT* cur = phead;
	while (cur)
	{
		if (cur->data == x)//等于要查找的元素就返回该节点的地址。
		{
			return cur;
		}
		cur = cur->next;//不等于就继续向下遍历
	}
	return NULL;
}
void SLTModify(SLT* phead, SLDataType x,SLDataType newdata)
{
	SLT* pnew = SLFind(phead, x);//与查找配合使用,找到了将对应节点的数据进行修改即可。
	pnew->data = newdata;
}
void SLTInsertFront(SLT** pphead,SLT* pos, SLDataType x)//在任意位置的前面插入,pos是通过查找操作得到的。
{
	assert(pphead);
	assert(pos);
	if (*pphead == NULL)
	{
		SLT* newnode = CreatNewnode(x);//如果单链表为空,就创建一个新节点进行头插
	    newnode->next=*pphead;
		*pphead=newnode;
	}
	else//不为空,
	{
		SLT* prev = *pphead;//从头结点开始遍历单链表
		while (prev->next != pos)
		{
			prev = prev->next;//找到pos的前一个节点停止
		}
		SLT* newnode = CreatNewnode(x);//创建新节点然后将其插入到pos的前面
		prev->next = newnode;
		newnode->next = pos;
	}
}
void SLTInsertAfter(SLT** pphead, SLT* pos, SLDataType x)//在任意位置的后面进行插入
{
	assert(pphead);
	assert(pos);
	SLT* newnode = CreatNewnode(x);
	newnode->next = pos->next;//先将pos后面的节点	连接到新节点的后面
	pos->next = newnode;//再将新建节点连接到pos之后。
}
void SLTErace(SLT** pphead, SLT* pos)//任意位置的删除
{
	assert(*pphead);
	assert(pos);
	if (pos == *pphead)//如果pos为头节点的位置,就头删
	{
		SLT* del = *pphead;
		*pphead = (*pphead)->next;
		free(del);
	}
	else//不在头节点的位置,就将pos之前的与pos之后的进行连接
	{
		SLT* prev = *pphead;//从头遍历,prev为pos的前一个节点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;//将pos之前的与pos之后的进行连接
		free(pos);//将pos的位置进行释放
	}
}
void SLTEraceAfter(SLT* pos)//删除任意位置之后的节点
{
	assert(pos);
	assert(pos->next);
	SLT* Next = pos->next;//找到pos之后的节点
	pos->next = Next->next;//将pos与pos之后的之后节点进行连接
	free(Next);//释放pos之后的空间
}

3.3【单链表的问题和缺点】

链表中存在的问题:

链表虽然是一种常用的数据结构,但它也存在一些问题:

  1. 内存空间不连续,导致缓存命中率低。链表中每个节点只能通过指针访问下一个节点,这说明它们在物理内存中的位置不是连续的,这意味着在遍历链表时,CPU无法预测下一个节点的内存地址,从而使得缓存无法有效地工作,访问每个节点都需要从主存中读取。

  2. 指针需要额外的内存空间。链表的一个节点需要存储数据和指向下一个节点的指针,这样的节点相对于数组节点来说,需要消耗更多的内存空间,当数据量很大时可能会引发内存问题。

  3. 查找效率低下。在链表中查找指定的元素需要遍历整个链表,因此其查找效率较低。

  4. 维护链表比较困难。链表的删除、插入等操作会涉及到指针的修改,因此在链表的维护过程中需要小心处理指针,防止出现指针漂移、泄露等问题,难点相对于数组维护大。

  5. 难以充分利用CPU缓存。链表的访问效率较低,会增加CPU换入/换出缓存的频次,进而减低性能。尽管现代CPU为了解决这个问题而配备了一级缓存和二级缓存等设施,但对于数据规模较大的大型链表,这些设施也无法发挥大的作用。

总的来说,链表需要花费更多的时间和空间来维护、操作和遍历,因此需要根据具体的应用场景选择不同的数据结构来实现需要的功能。

链表的缺点

  1. 链表不支持随机访问,只能顺序访问。因为链表中每个节点只保存了下一个节点的位置,没有保存前一个节点的位置,所以没法像数组一样随机访问元素,需要从头遍历链表,直到找到需要的元素。因此,链表中的查找、删除和插入操作的时间复杂度比较高。

  2. 链表需要额外的存储空间来表示每个节点指向下一个节点的指针,比顺序表占用更多的内存空间。

  3. 在进行插入操作时,由于链表中需要修改前一个节点的指向,因此需要更复杂的操作,包括考虑头插和尾插等不同的情况。

3.4【单链表练习题目】

题目1:

链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

分析与解答:

1.常规思路:
遍历链表把值为val的节点进行释放发删除。

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* cur = head;//定义两个节点,cur用来遍历链表,prev用来保存cur的前一个节点
    struct ListNode* prev = NULL;//cur从头节点开始遍历,那么头节点的前一个节点为空
    while (cur)//遍历链表cur到末尾停止
    {
        if (cur->val == val)//每次判断cur的值是否为val,如果是则会有两种情况
        {第一种情况,头节点对应的值为val,为特殊情况。
            if (prev == NULL)//判断链表的头节点对应的值是否为val,如果是进行头删即可。
            {
                cur = head->next;
                free(head);
                head = cur;
            }第二种情况,头节点之后的节点对应的值为val,为一般情况
            else//如果不是,则将cur对应的空间进行删除释放。
            {
                prev->next = cur->next;//首先利用prev将cur的前一个节点和cur的后一个节点进行连 
                                       //接。
                free(cur);//释放cur
                cur = prev->next;//将cur变为其后一个节点。
            }
        }
        else//如果cur对应的值不是val,则将cur向后遍历
        {
            prev = cur;//每次迭代cur之前用prev保存其前一个节点。
            cur = cur->next;//迭代cur

        }

    }
    return head;//最后返回链表的头节点即可。

}

2.遍历链表,将不是val的进行尾插。

struct ListNode* removeElements(struct ListNode* head, int val)
{
     struct ListNode* newhead=NULL;//定义两个新节点,newhead,tail用于尾插
     struct ListNode* tail=NULL;
     struct ListNode* cur=head;//定义cur用来遍历链表
     while(cur)//使用cur遍历直到链表最后一个节点
     {
         if(cur->val==val)
         {
             struct ListNode* del=cur;//如果遍历过程中有节点的值为val,则进行删除释放
             cur=cur->next;//cur继续向后走
             free(del);
         }
         else //遍历过程中,对于对应节点不等于val的,将其尾插在newhaed之后,这里tail的作用是
         {    //每次在尾插以后将尾进行记录,防止下次在进行尾插还要找尾的操作。
             if(newhead==NULL)//如果是第一次尾插,因为这里newhead和tail都为空指针,则应该先为其
             {                //赋值为cur,而不是直接进行尾插,直接尾插会存在对空指针的解引用。
                 newhead=tail=cur;
             }
             else //这里是正常尾插的情况,将每次遍历的cur中对应值不是val的节点尾插在tail之后
             {    //尾插以后再将tail向后移动使其变为新的尾
                 tail->next=cur;
                 tail=tail->next;
             }
             cur=cur->next;//这里对cur进行迭代
         }
     }
     if(tail)//空链表时tail为空
        {
            tail->next=NULL; //在遍历完成之后,由于tail的next还是指向在cur对应的节点,如果出现
        }                    //链表最后1个节点对应的值是val,那么对其释放此时cur对应的节点已经被 
                             //释放,但是tail的next仍然是指向cur对应的节点,此时其为野指针,所 
                             //以应该把newhead对应的新链表尾部让其指向NULL
     return newhead;
}

题目2:

链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

.分析与解答:

1.向后遍历链表进行头插。

struct ListNode* reverseList(struct ListNode* head)
{
    
    struct ListNode* cur=head;//定义cur用来遍历链表
    struct ListNode* rehead=NULL;定义新的头节点用来头插
    
    while(cur)//遍历链表
    {
        struct ListNode* tail=cur->next;//首先保存cur下一个结点的值,以便进行迭代
        cur->next=rehead;//将cur对应的节点与rehead进行连接,完成头插。
        rehead=cur;然后将rehead变为新的头节点
        cur=tail;再将cur后移到下一个节点。

    }
    return rehead;//最后返回新的头节点

}

2.改变各个节点之间的指向:
 

struct ListNode* reverseList(struct ListNode* head)
{
    if(head==NULL)
    {
        return NULL;
    }
    struct ListNode* rehead=NULL;//定义新的头节点并为其赋值为空
    struct ListNode* cur=head;//定义cur从而遍历链表
    struct ListNode* tail=cur->next;//定义tail用来保存cur的下一个节点
    while(cur)
    {
        cur->next=rehead;改变cur的指向
        rehead=cur;//将头节点进行更新
        cur=tail;//跌带cur
        if(tail)//当cur来到最后一个节点时,tail为空,此时不能将tail向下迭代了,否则会出现对空指针
        {       //的解引用
            tail=tail->next;
        }
    }
    return rehead;//最后返回新的头节点
}

题目3:

链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

分析与解答:

快慢指针分别遍历链表

struct ListNode* middleNode(struct ListNode* head)
{
   struct ListNode* slow=head;
   struct ListNode* fast=head;
   while(fast&&fast->next)//fast对应奇数个的节点,fast—>next对应偶数个
   {//
       fast=fast->next->next;//快指针每次遍历两个节点
       slow=slow->next;//,慢指针每次遍历一个,
   } //快指针走完,慢指针刚好走一半
   return slow;//最后返回慢指针
}

题目4:

链接:链表中倒数第k个结点_牛客题霸_牛客网

分析与解答:

利用快慢指针之间的差值:

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    struct ListNode* fast=pListHead;
    struct ListNode* slow=pListHead;
    while(k--)//k表示倒数第k个节点
    {
        if(fast==NULL)防止k的大小超过链表节点数,如果超过了就返回空
        {
            return NULL;
        }
        fast=fast->next;//拉开fast与slow之间的距离,这样fast遍历到尾节点时,slow刚好到倒数第k个 
                        //节点
    }
    while(fast)//向后遍历,fast到尾节点时,slow也到倒数第k个节点了
    {
        fast=fast->next;
        slow=slow->next;
    }
    return slow;//最后返回slow即可
}

题目5:

链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

分析与解答:

分别比较,取小的尾插

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
 {
    struct ListNode* head=NULL;
    struct ListNode* tail=NULL;
    if(list1==NULL)//如果list1为空链表,就直接返回list2
       return list2;
    if(list2==NULL)//反之亦然
       return list1;
    while(list1 && list2)//当两个链表有一个被遍历完就停止循环
    {
        if(list1->val < list2->val)//分别比较两个链表各节点的值,取小的尾插。
        {
            if(tail==NULL)//如果是第一次尾插,直接进行赋值
            {
                tail=head=list1;
            }
            else//不是第一次就尾插比较之后较小的值
            {
                tail->next=list1;
                tail=tail->next;
            }
            list1=list1->next;
        }
        
        else
        {
            if(tail==NULL)
            {
                tail=head=list2;
            }
            else
            {
                tail->next=list2;
                tail=tail->next;
            }
            list2=list2->next;
        }
    }
    if(list1)//最后当有一个链表被遍历完,剩余的链表直接尾插在新链表的后面。
       tail->next=list1;
    if(list2)
        tail->next=list2;
    return head;
}

题目6:

链接:链表分割_牛客题霸_牛客网

分析与解答:

class Partition 
{
public:
    ListNode* partition(ListNode* pHead, int x) 
    {
//分别将数据小于x的节点和大于x的节点分别链接,形成两个子链表,链接的过程中采用尾插,这样可以不改变
//相对位置,最后将两个字链表进行链接即可。
        struct ListNode* lesshead,*lesstail,*greathead,*greattail;
//开辟两个额外的空间,并分别用lesshead,lesstail和greathead,greattail来管理,
//其中lesshead和greathead是两个哨兵卫,使用结束后将其释放即可。
        lesshead=lesstail=(struct ListNode*)malloc(sizeof(struct ListNode));
        greathead=greattail=(struct ListNode*)malloc(sizeof(struct ListNode));
        struct ListNode* cur=pHead;
//定义cur来遍历链表,从而达到将数据小于x的节点和数据大于x的节点进行分离。
        while(cur)
        {
            if(cur->val<x)
            {
                lesstail->next=cur;
                lesstail=lesstail->next;//将数据小于x的节点尾插在lesshead之后,每次插入后
//将lesstail向后一个节点。
            }
            else 
            {
                greattail->next=cur;
                greattail=greattail->next;//将数据大于x的节点尾插在lesshead之后,每次插入后
//将lesstail向后一个节点。

            }
            cur=cur->next;//对cur进行迭代
        }
        lesstail->next=greathead->next;//将两个子链表进行链接,注意greathead是哨兵卫,
//应该连接在它后一个节点上。
        greattail->next=NULL;//最后一个大于x,数据的节点虽然尾插在了geattail后但是它的next
//仍然指向它之前母链表的上一个节点,所以需要对其进行置空。
        pHead=lesshead->next;//将链接后的新链表赋值给pHead,lesshead是哨兵卫,需连接在它下一个 //节点。
        free(lesshead);//释放额外开辟的两个空间
        free(greathead);
        return pHead;//返回合并后的链表的头。

    }
};

题目7:

链接:链表的回文结构_牛客题霸_牛客网

分析与解答:

class PalindromeList {
public:
//回文表示对称,即判断链表是否对称,可以先找到链表的中间节点,从中间节点开始逆置链表,
//然后一个从头开始,一个从逆置后的头节点开始,依次对链表各节点的数据进行遍历比较,如果都相等
//说明是回文结构。否则不是
    struct ListNode* middleNode(struct ListNode* head)//查找中间节点的函数
    {
        struct ListNode* slow=head;
        struct ListNode* fast=head;
        while(fast&&fast->next)
        {
          fast=fast->next->next;
          slow=slow->next;
        } 
        return slow;
    }
   struct ListNode* reverseList(struct ListNode* head)//逆置链表的函数
    {
    
        struct ListNode* cur=head;
        struct ListNode* rehead=NULL;
    
        while(cur)
       {
          struct ListNode* tail=cur->next;
          cur->next=rehead;
          rehead=cur;
          cur=tail;

       }
         return rehead;

    }


    bool chkPalindrome(ListNode* A) //判断是否回文的函数,返回值为布尔值
    {
        // write code here
        struct ListNode* mid=middleNode(A);//调用查找中间节点的函数
        struct ListNode* rmid=reverseList(mid);//调用逆置链表的函数
        while(A&&rmid)//注意,逆置以后中间节点的next指向NULL,当遍历到中间节点
//循环即可停止
        {
            if(rmid->val!=A->val)//判断数据是否相等。
            {
                return false;
            }
            else
            {
                rmid=rmid->next;
                A=A->next;
            }
        }//循环结束,未出现数据不相等的情况,就是回文结构。
        return true;

    }
};

题目8:

链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

分析与解答:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
//判断相交的条件:尾节点相等,让长的链表先开始走,直到与短的长度相同,两个链表同时走,遍历到出现
//第一个相等的节点就返回该节点。
  int lena=1;//定义lena记录A链表的长度
  int lenb=1;//定义lenb记录B链表的长度
  struct ListNode* cura=headA;//定义cura遍历A链表,并记录长度
  struct ListNode* curb=headB;//定义curb遍历B链表,并记录长度
  while(cura->next)
  {
      cura=cura->next;//遍历A链表
      lena++;//每遍历一个节点长度加1
  }  
  while(curb->next)
  {
      curb=curb->next;
      lenb++;//同上
  }
  if(cura!=curb)//此时cura和curb分别指向A,B链表的尾节点,如果不相等,说明不会相交
  {
      return NULL;//不相交就返回NULL;
  }
  int gap=abs(lena-lenb);//计算差值,以便将长链表和短链表处在同一起跑线
  struct ListNode* longlist=headA;//假定A为长链表
  struct ListNode* shortlist=headB;//B为短链表
  if(lena<lenb)//如果假定错误,则颠倒
  {
      longlist=headB;
      shortlist=headA;
  }
  while(gap--)//先让长的走差距步
  {
      longlist=longlist->next;
  }
  while(shortlist!=longlist)//处于同一起跑线时,一起向后遍历,出现第一个相等的节点,
//返回这个节点,注意,不相交的情况在前面的if(cura!=curb)已经判断过,执行到这里就一定能够
//相交,相交返回第一个相交节点即可。
  {
      longlist=longlist->next;
      shortlist=shortlist->next;

  }
  return longlist;


}

相交的分析:

四、【双链表】

4.1【带头双链表的介绍】

我么们上面讲解了单链表的定义及其实现,我们了解到,单链表是通过,指针将每个节点连接在一起的,也就是,每个节点都存储了下个结点的地址,可是,在我们进行删除操作时,会带来一些不方便,因为删除要将pos的前一个结点和pos的后一个节点进行连接,我们要额外定义一个指针用来记录pos前一个结点的位置,或带来一些不便,这里我们就可以用带头双链表来代替单链表,这里的“头”指的是哨兵卫,其内部不存储有效数据,双链表的每个节点内部都存放着,其上一个节点的指针和其下一个节点的指针,从而达到双向的目的。

4.2【带头双向链表的实现】

DSList.h

#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int DLDatatype;
typedef struct DSList
{
	struct DSList* prev;
	struct DSList* next;
	DLDatatype data;
}DSL;
DSL* Init();
void PushFront(DSL* phead, DLDatatype x);
void PushBack(DSL* phead, DLDatatype x);
void PrintDSL(DSL* phead);
void PopFront(DSL* phead);
void PopBack(DSL* phead);
DSL*  DSLFind(DSL* phead, DLDatatype x);
void DSLModify(DSL* phead, DLDatatype x);
void DSLInsert(DSL* pos, DLDatatype x);
void DSLErase(DSL* pos);
void DSLDestroy(DSL* phead);

DSList.c

#define _CRT_SECURE_NO_WARNINGS
#include "DSList.h"
DSL* CreaNode(DLDatatype x)
{
	DSL* newnode = (DSL*)malloc(sizeof(DSL));
	if (newnode == NULL)
	{
		perror("malloc,failed");
		return NULL;
	}
	else
	{
		newnode->data = x;
		newnode->prev = NULL;
		newnode->next = NULL;
		return newnode;
	}
}
DSL* Init()
{
	DSL* phead = CreaNode(-1);
	phead->prev = phead;
	phead->next = phead;
	return phead;
}
bool DSLEmpty(DSL* phead)
{
	assert(phead);
	return phead->next == phead;
}
void PushFront(DSL* phead, DLDatatype x)
{
	assert(phead);
	DSL* newnode = CreaNode(x);
	DSL* head = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = head;
	head->prev = newnode;
}
void PushBack(DSL* phead, DLDatatype x)
{
	assert(phead);
	DSL* newnode = CreaNode(x);
	DSL* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}
void PrintDSL(DSL* phead)
{
	assert(phead);
	printf("guard<-->");
	DSL* cur = phead->next;
	while (cur!=phead)
	{
		printf("%d<-->",cur->data);
		cur = cur->next;
	}
	printf("\n");
}
void PopFront(DSL* phead)
{
	assert(phead);
	assert(!DSLEmpty(phead));
	DSL* Next = phead->next;
	phead->next = Next->next;
	Next->next->prev = phead;
	free(Next);
}
void PopBack(DSL* phead)
{
	assert(phead);
	assert(!DSLEmpty(phead));
	DSL* tail = phead->prev;
	DSL* newtail = tail->prev;
	newtail->next = phead;
	phead->prev = newtail;
	free(tail);
}
DSL*  DSLFind(DSL* phead, DLDatatype x)
{
	assert(phead);
	DSL* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
void DSLModify(DSL* pos, DLDatatype x)
{
	assert(pos);
	pos->data = x;
}
void DSLInsert(DSL* pos, DLDatatype x)
{
	assert(pos);
	DSL* newnode = CreaNode(x);
	DSL* posprev = pos->prev;
	posprev->next = newnode;
	newnode->prev = posprev;
	newnode->next = pos;
	pos->prev = newnode;

}
void DSLErase(DSL* pos)
{
	assert(pos);
	DSL* posnext = pos->next;
	DSL* posprev = pos->prev;
	posprev->next = posnext;
	posnext->prev = posprev;
	free(pos);
}
void DSLDestroy(DSL* phead)
{
	assert(phead);
	DSL* cur = phead->next;
	while (cur != phead)
	{
		DSL* Next = cur->next;
		cur->data = 0;
		free(cur);
		cur = Next;

	}
	free(phead);
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include "DSList.h"
void test1()
{
	DSL* pdlist = Init();
	PushBack(pdlist, 1);
	PushBack(pdlist, 2);
	PushBack(pdlist, 3);
	PushBack(pdlist, 4);
	PrintDSL(pdlist);
	PushFront(pdlist, 6);
	PushFront(pdlist, 8);
	PushFront(pdlist, 10);
	PushFront(pdlist, 12);
	PrintDSL(pdlist);
	PopBack(pdlist);
	PopBack(pdlist);
	PopBack(pdlist);
	PrintDSL(pdlist);
	PrintDSL(pdlist);
	DSL* pos = DSLFind(pdlist, 6);
	if (pos)
	{
		DSLModify(pos, 200);
	}
	PrintDSL(pdlist);
	pos = DSLFind(pdlist, 200);
	if (pos)
	{
		DSLInsert(pos, 300);
	}
	PrintDSL(pdlist);
	pos = DSLFind(pdlist, 300);
	if (pos)
	{
		DSLErase(pos);
	}
	PrintDSL(pdlist);
	DSLDestroy(pdlist);
	pdlist = NULL;
}
int main()
{
	test1();
	
	return 0;
}

4.2【双链表的问题和缺点】

相对于其他的链表结构,带头双向链表几乎没有问题和缺点,如果硬要找一个的话,只能说双链表的结构比较复杂,但是虽然结构比较复杂,双链表确实是链表所有种类中实现起来最简单的。

4.3【顺序表和链表的区别】

这里参与比较的主要还是顺序表和带头双向链表

4.5【链表的一些经典试题】

1.链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

【思路】
快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,
如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。

【分析与解答】

bool hasCycle(struct ListNode *head) //用布尔值来接收函数的返回值
{
    struct ListNode* fast=head,*slow=head;//定义快慢指针,如果有环则会在环中相遇,无环
//则快指针会优先走到NULL
    while(fast&&fast->next)
    {
        slow=slow->next;//慢指针一次走一步,快指针一次走两步
        fast=fast->next->next;
        if(slow==fast)//只要存在环,快指针一定会追上慢指针
        {
            return true;//追上了说明存在环,就返回true
        }
    }
    return false;//快指针走到了NULL,说明无环
}

【扩展问题】
1.为什么快指针每次走两步,慢指针走一步可以?
假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚
进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。
此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情
况,因此:在满指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。
2.快指针一次走3步,走4步,...n步行吗?

2.链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

【思路】

让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环
运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇。

【证明】

【分析与解答】

【解法(1)】:

定义一个一个指针从相遇点开始走,另一个指针从头开始走它们会在入口点相遇。

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* fast=head,*slow=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(slow==fast)
        {
            struct ListNode* meet=slow;//相遇点指针
            while(head!=meet)
            {
                head=head->next;//头节点和相遇点同时走
                meet=meet->next;
            }
            return meet;//再次相遇就是入口点
        }
    }
    return NULL;//不是环,直接返回NULL
}

【解法(2)】:

将首次相遇点之后的节点先保存再置空,把环解开将其转换为链表相交问题

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{//解决相交问题的函数
  struct ListNode* lista=headA;
  struct ListNode* listb=headB;
  int lena=1;
  int lenb=1;
  while(lista)
  {
      lena++;
      lista=lista->next;
  }
  while(listb)
  {
      lenb++;
      listb=listb->next;
  }
  if(lista!=listb)
  {
      return NULL;
  }
  int len=abs(lena-lenb);
  struct ListNode* longlist=headA;
  struct ListNode* shortlist=headB;
  if(lenb>lena)
  {
      longlist=headB;
      shortlist=headA;
  }
  while(len--)
  {
      longlist=longlist->next;
  }
  while(shortlist!=longlist)
  {
      shortlist=shortlist->next;
      longlist=longlist->next;
  }
  return longlist;

}
struct ListNode* detectCycle(struct ListNode *head) {
    struct ListNode* fast=head,*slow=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(slow==fast)
        {
            struct ListNode* meet=slow;
            struct ListNode* meetnext=meet->next;
            meet->next=NULL;//解开环
            return getIntersectionNode(head,meetnext);//调用解决相交问题的函数
        }
    }
    return NULL;
}

3.链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

【分析与解答】

思路:

1、拷贝节点插入到原节点的后面

2、控制拷贝节点的random

3、拷贝节点从原链表上解下,组成拷贝链表并恢复原来链表



struct Node* copyRandomList(struct Node* head)
{
	struct Node* cur=head;
    while(cur)
    {
        struct Node* copy=(struct Node*)malloc(sizeof(struct Node));
        struct Node* Next=cur->next;
        copy->val=cur->val;
        cur->next=copy;
        copy->next=Next;
        cur=Next;
    }
    cur=head;
    while(cur)
    {
        struct Node* copy=cur->next;
        if(cur->random==NULL)
        {
            copy->random=NULL;
        }
        else
        {
            copy->random=cur->random->next;
        }
        cur=copy->next;
    }
    struct Node* copyhead=NULL,*copytail=NULL;
    cur=head;
    while(cur)
    {
        struct Node* copy=cur->next;
        struct Node* Next=copy->next;
        if(copytail==NULL)
        {
            copyhead=copytail=copy;
        }
        else
        {
            copytail->next=copy;
            copytail=copytail->next;


        }
        cur->next=Next;
        cur=Next;
    }
    return copyhead;
}

五、【栈和队列】

5.1【栈的介绍】

【栈的概念及其结构】

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端
称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。

5.2【栈的实现】

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

【代码部分】

【头文件Stack.h】

#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
typedef int STDataType;
typedef struct Stack
{
	STDataType* pda;
	int top;
	int capacity;
}ST;
void STInit(ST* pst);
void STDestroy(ST* pst);
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
bool STEmpty(ST* pst);
int STSize(ST* pst);

【功能模块Stack.c】

#define _CRT_SECURE_NO_WARNINGS
#include "Stack.h"
void STInit(ST* pst)
{
	assert(pst);
	pst->pda = NULL;
	pst->top = 0;
	pst->capacity = 0;
}
void STDestroy(ST* pst)
{
	assert(pst);
	free(pst->pda);
	pst->pda = NULL;
	pst->top=pst->capacity = 0;
}
void STPush(ST* pst, STDataType x)
{
	if (pst->top == pst->capacity)
	{
		int newcapacity = (pst->capacity == 0) ? 4 : 2 * (pst->capacity);
		STDataType* tmp = (STDataType*)realloc(pst->pda, newcapacity*sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc failed");
			return;
		}
		pst->pda = tmp;
		pst->capacity = newcapacity;
	}
	pst->pda[pst->top] = x;
	pst->top++;
}
void STPop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	pst->top--;
}
STDataType STTop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	return pst->pda[pst->top - 1];
}
bool STEmpty(ST* pst)
{
	assert(pst);
	return (pst->top == 0);
}
int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}

【测试部分test.c】

#define _CRT_SECURE_NO_WARNINGS
#include "Stack.h"
void test1()
{
	ST st;
	STInit(&st);
	STPush(&st, 1);
	STPush(&st, 2);
	STPush(&st, 3);
	STPush(&st, 4);
	STPush(&st, 5);
	while (!STEmpty(&st))
	{
		printf("%d ", STTop(&st));
		STPop(&st);
	}
	STDestroy(&st);

}
int main()
{
	test1();
	

	return 0;
}

5.3【栈练习题目】

题目1:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
typedef char STDataType;
typedef struct Stack
{
	STDataType* pda;
	int top;
	int capacity;
}ST;
void STInit(ST* pst);
void STDestroy(ST* pst);
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
bool STEmpty(ST* pst);
int STSize(ST* pst);
void STInit(ST* pst)
{
	assert(pst);
	pst->pda = NULL;
	pst->top = 0;
	pst->capacity = 0;
}
void STDestroy(ST* pst)
{
	assert(pst);
	free(pst->pda);
	pst->pda = NULL;
	pst->top=pst->capacity = 0;
}
void STPush(ST* pst, STDataType x)
{
	if (pst->top == pst->capacity)
	{
		int newcapacity = (pst->capacity == 0) ? 4 : 2 * (pst->capacity);
		STDataType* tmp = (STDataType*)realloc(pst->pda, newcapacity*sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc failed");
			return;
		}
		pst->pda = tmp;
		pst->capacity = newcapacity;
	}
	pst->pda[pst->top] = x;
	pst->top++;
}
void STPop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	pst->top--;
}
STDataType STTop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	return pst->pda[pst->top - 1];
}
bool STEmpty(ST* pst)
{
	assert(pst);
	return (pst->top == 0);
}
int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}

bool isValid(char* s)
{
    ST st;
    STInit(&st);
    while(*s)
    {
        //左括号入栈
        if(*s=='('||*s=='['||*s=='{')
        {
            STPush(&st,*s);

        }
        else
        {
            //右括号就出栈比较
            if(STEmpty(&st))
            {
                return false;
            }
            char tmp=STTop(&st);
            STPop(&st);
            if(*s==')'&&tmp!='('||*s==']'&&tmp!='['||*s=='}'&&tmp!='{')
            {
                return false;
            }
           
        }
        s++;
    }
    bool flag=STEmpty(&st);
    STDestroy(&st);
    return flag;

}

题目2:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

typedef int STDataType;
typedef struct Stack
{
	STDataType* pda;
	int top;
	int capacity;
}ST;
void STInit(ST* pst);
void STDestroy(ST* pst);
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
bool STEmpty(ST* pst);
int STSize(ST* pst);
void STInit(ST* pst)
{
	assert(pst);
	pst->pda = NULL;
	pst->top = 0;
	pst->capacity = 0;
}
void STDestroy(ST* pst)
{
	assert(pst);
	free(pst->pda);
	pst->pda = NULL;
	pst->top=pst->capacity = 0;
}
void STPush(ST* pst, STDataType x)
{
	if (pst->top == pst->capacity)
	{
		int newcapacity = (pst->capacity == 0) ? 4 : 2 * (pst->capacity);
		STDataType* tmp = (STDataType*)realloc(pst->pda, newcapacity*sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc failed");
			return;
		}
		pst->pda = tmp;
		pst->capacity = newcapacity;
	}
	pst->pda[pst->top] = x;
	pst->top++;
}
void STPop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	pst->top--;
}
STDataType STTop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	return pst->pda[pst->top - 1];
}
bool STEmpty(ST* pst)
{
	assert(pst);
	return (pst->top == 0);
}
int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}


typedef struct {
    ST pushst;
    ST popst;
} MyQueue;


MyQueue* myQueueCreate() {
    MyQueue* obj=(MyQueue* )malloc(sizeof(MyQueue));
    if(obj==NULL)
    {
        perror("malloc fail");
        return NULL;
    }
    STInit(&(obj->pushst));
    STInit(&(obj->popst));
    return obj;
}

void myQueuePush(MyQueue* obj, int x) {
    STPush(&obj->pushst,x);
}
int myQueuePeek(MyQueue* obj) {
    if(STEmpty(&obj->popst))
    {
        while(!STEmpty(&obj->pushst))
        {
            STPush(&obj->popst,STTop(&obj->pushst));
            STPop(&obj->pushst);

        }
    }
    return STTop(&obj->popst);
}

int myQueuePop(MyQueue* obj) {
    int front=myQueuePeek(obj);
    STPop(&obj->popst);
    return front;
}


bool myQueueEmpty(MyQueue* obj) {
    return STEmpty(&obj->popst)&&STEmpty(&obj->pushst);
}

void myQueueFree(MyQueue* obj) {
    STDestroy(&obj->pushst);
    STDestroy(&obj->popst);
    free(obj);

}

5.4【队列的介绍】

【队列的概念及结构】

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出即FIFO(First In First Out) 的特性,入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头。

5.5【队列的实现】

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

【代码部分】

【Queue.h】

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq,QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);

【Queue.c】

#define _CRT_SECURE_NO_WARNINGS
#include "Queue.h"
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* Next = cur->next;
		free(cur);
		cur = Next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq,QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	if (pq->phead == NULL)
	{
		assert(pq->ptail == NULL);
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next=newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		QNode* Next = pq->phead->next;
		free(pq->phead);
		pq->phead = Next;
	}
	pq->size--;
}
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->ptail->data;
}
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return (pq->phead == NULL && pq->ptail == NULL);
}

【test.c】

#define _CRT_SECURE_NO_WARNINGS
#include "Queue.h"
void test1()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	printf("%d ", QueueFront(&q));
	QueuePop(&q);
	QueuePush(&q, 4);
	QueuePush(&q, 5);
	printf("SIZE:%d\n", QueueSize(&q));
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");
	QueueDestroy(&q);
}
int main()
{
	test1();
	return 0;
}

5.6【队列的拓展】

另外扩展了解一下,实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。

5.7【队列练习题目】

题目1:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq,QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* Next = cur->next;
		free(cur);
		cur = Next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq,QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	if (pq->phead == NULL)
	{
		assert(pq->ptail == NULL);
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next=newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		QNode* Next = pq->phead->next;
		free(pq->phead);
		pq->phead = Next;
	}
	pq->size--;
}
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->ptail->data;
}
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return (pq->phead == NULL && pq->ptail == NULL);
}



typedef struct {
    Queue q1;
    Queue q2;
} MyStack;


MyStack* myStackCreate() {
    MyStack* obj=(MyStack*)malloc(sizeof(MyStack));
    if(obj==NULL)
    {
        perror("malloc fail");
        return NULL;
    }
    QueueInit(&(obj->q1));
    QueueInit(&(obj->q2));
    return obj;
}

void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
        QueuePush(&obj->q2,x);
    }
}

int myStackPop(MyStack* obj) {
    Queue* pEmpty=&obj->q1;
    Queue* pNoempty=&obj->q2;
    if(!QueueEmpty(&obj->q1))
    {
        pEmpty=&obj->q2;
        pNoempty=&obj->q1;
    }
    while(QueueSize(pNoempty)>1)
    {
        QueuePush(pEmpty,QueueFront(pNoempty));
        QueuePop(pNoempty);
    }
    int top=QueueFront(pNoempty);
    QueuePop(pNoempty);
    return top;
}

int myStackTop(MyStack* obj) {
    if(!QueueEmpty(&obj->q1))
    {
        return QueueBack(&obj->q1);

    }
    else
    {
        return QueueBack(&obj->q2);
    }
}

bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj) {
    QueueDestroy(&obj->q1);
    QueueDestroy(&obj->q2);
    free(obj);
}

题目2:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

typedef struct {
    int front;
    int rear;
    int k;
    int* a;

} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->a=(int* )malloc(sizeof(int)*(k+1));
    obj->front=0;
    obj->rear=0;
    obj->k=k;
    return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front==obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->rear+1)%(obj->k+1)==obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
    {
        return false;
    }
    obj->a[obj->rear]=value;
    obj->rear++;
    obj->rear%=(obj->k+1);
    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    obj->front++;
    obj->front%=(obj->k+1);
    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    return obj->a[obj->front];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    return obj->a[(obj->rear+obj->k)%(obj->k+1)];
}




void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

总结

本篇博客到这就结束了,感谢观看!


........................................................................................................................再回童年 敲敲门

                                                                                                              ————《床边故事》

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值