《啊哈算法》第二章--队列 栈 链表

在这里插入图片描述

文章目录

  • 前言
  • 一、数据结构基础知识(衔接知识)
  • 二、队列
  • 三、栈
  • 四、链表
  • 总结


前言

上一节我们学习了排序算法当中的快速排序 冒泡排序 桶排序 ,那么本节得主要学习内容是队列 栈 链表得相关数据结构得知识


一、数据结构基础知识(衔接知识)

基于学习这本书得都是一些算法小白甚至是小学生学习 所以我想补充一些 数据结构得基础知识给大家,以便于大家更好的理解本节得知识

1. 栈中的“先进后出,后进先出”什么意思

栈对于数据的管理主要有两种操作:

  1. 压栈:栈的插入操作叫做进栈 / 压栈 / 入栈,从栈顶进行压栈。
  2. 出栈:栈的删除操作叫做 出栈,从栈顶进行出栈。
    《啊哈算法》第二章--队列 栈 链表_第1张图片

2.栈的定义
栈只允许在固定的一段进行插入和删除元素的操作。进行数据插入和删除操作的一端称为栈顶,不进行操作的一端称为栈底栈中的元素遵守 后进先出 (LIFO - Last In First Out) 的原则。也就是先进的后出,后进的先出

3,栈与队列的区别
形象得比喻就是栈就像子弹一样,最先进去的最后被射出来,而队列则是排队内样,先到先得 这就是先进先出,也叫后进后出

看完这些如果对队列 和 栈 想进一步了解的伙伴可以 看我下面两篇博客 进行学习
【数据结构】千字深入浅出讲解队列(附原码 | 超详解)

【数据结构】千字深入浅出讲解栈(附原码 | 超详解)

二、队列

队列的定义

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出

FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头

数据结构图如下:
《啊哈算法》第二章--队列 栈 链表_第2张图片
队首(head)删除数据,称之为“出队”
队尾(tail)插入数据,称之为“入队”
当队列中没有元素时(head == tail),称为空队列

题目描述

新学期开始,小哼和小哈俩人坐同桌然后小哼问小哈QQ号码是多少,但是小哈不轻易给别人QQ号就给了一串神秘数字,但是数字是有规则的数字

规则是先将第1个数删除,然后把第2个数放到这串数末尾,再将第3个数删除,并把第4个数放到末尾…

直到剩下最后一个数,把最后一个数也删除。把删除的数连在一起就是小哈的QQ号了。小哈给他加密过的一串数是“920430714”,求小哈的QQ号是多少?

解题思路
《啊哈算法》第二章--队列 栈 链表_第3张图片
《啊哈算法》第二章--队列 栈 链表_第4张图片

队首删除一个数的操作就是head++
队尾增加一个数的操作就是q[tail]=x , tail++

现在有9个数,把9个数放入队列后,head == 1,tail == 10,此时head和tail之间的数就是目前队列中“有效”的数

代码如下

//方法一:
#include
using namespace std;
int main()
{
	int q[102]={0,6,3,1,7,5,8,9,2,4},head,tail;
	//初始化队列
	head=1;//指向第一个
	tail=10;//指向最后一个的下一个
	while(head<tail)//队列不为空
	{
		//打印队首
		printf("%d ",q[head]);
		head++;//队首出队 
		
		//先将新队首得数添加到队尾
		q[tail]=q[head];
		tail++;
		//再将队首出队 
		head++; 
	}
	return 0;
}

样例实列

输入:6 3 1 7 5 8 9 2 4
输出:6 1 5 9 4 7 2 8 3

将队列三个基本元素(一个数组,两个变量)封装为一个结构体类型,其实在算法比赛当中不应该使用结构体 而是采用第一种方法。

//方法二

#include
using namespace std;
struct queue
{
	int data[100];
	int head;
	int tail;
};
int main()
{
	struct queue q;
	int i;
	//初始化队列
	q.head=1;
	q.tail=1;
	for(int i=1;i<=9;++i)
	{
		//依次向队列插入9个数
		cin>>q.data[q.tail];
		q.tail++; 
	}
	while(q.head<q.tail)
	{
		//打印队首并将队首出队
		cout<<q.data[q.head]<<" ";
		q.head++;
		q.data[q.tail]=q.data[q.head];
		q.tail++;
		q.head++; 
	}
	return 0;
}

三、栈

栈后进先出,生活中有很多例子:

1. 浏览网页退回之前某个网页,需要一步步点击后退键
2. 给弹夹装子弹,最后装入的子弹是第一个被打出去的,或者说第一个被退出来的
3. 又比如一个小桶,小桶的直径只能放入一个球,依次放入2,1,3号小球,想取出2号球就得先将3号取出来,再将1号取出来,最后才能取最先放进去的2号小球

《啊哈算法》第二章--队列 栈 链表_第5张图片
本节栈当中主要出现了一个问题 就是判断回文字符串 其实这个题目早在之前学习C语言的时候我就写过了,牛客上是有这个题目的

解题思路:
对于字符串进行一分为二,中点设置为mid,mid左边的字符串入栈进入到栈里面,然后入栈后让栈里面的字母和mid后面的字符进行匹配,设置Top,每一次匹配成功Top–,最后看Top是否为0,为0就代表是回文,否则不是回文字符

入栈的操作:s[++top] = a[i];
通过strlen(a)得到字符串长度len,mid = len / 2 - 1;

代码如下:

#include
#include
using namespace std;

char a[110],s[110];
int len,mid,next,top;

int main()
{
	cin.get(a,110);//读入字符串 
	len=strlen(a);//求字符串长度 
	mid=len/2-1;//求字符串的中点
	//入栈操作
	for(int i=0;i<=mid;++i)
	{
		s[++top]=a[i];
	}
	//判断字符串的长度是奇数还是偶数,并找出需要进行字符匹配的下标
	if(len%2==0) next=mid+1;
	else next=mid+2; 
	
	//开始匹配
	for(int i=next;i<=len-1;++i)
	{
		if(a[i]!=s[top]) break;
		top--;
	}
	
	if(top==0)	cout<<"Yes"<<endl;
	else cout<<"No"<<endl;
	return 0;
}

样例实列

输入:nihaooahin
输出:Yes

下面讲解一下不用栈的思路:

双指针算法,分别指向字符串的左侧和右侧进行相向而行,一个一个匹配,俩指针所指向的字符不一样的时候就输出NO,当两个指针相遇或者穿过彼此的时候,就输出Yes

代码如下:

#include
#include
using namespace std;
char a[110];
int is_huiwen(int left,int right)
{
	while(left<right)
	{
		if(a[left++]!=a[right--]) return 0;
		else continue;
	}
	return 1;
}
int main()
{
	cin.get(a,110);
	int right=strlen(a)-1;
	if(is_huiwen(0,right)) cout<<"Yes"<<endl;
	else cout<<"No"<<endl;
	return 0;
}

四、链表

链表的定义

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

《啊哈算法》第二章--队列 栈 链表_第6张图片

实现链表

使用指针和动态分配内存函数malloc来实现

1. 指针
详细看博客:C语言—指针初阶—总结

2. malloc函数
1malloc(sizeof(int))
等价于 malloc(4); 从内存中申请分配4个字节大小的内存空间
2现在已经成功从内存中申请了4个字节的空间来存放一个整数,如何对这个空间操作呢?
这就需要一个指针来指向这个空间,即存储这个空间的首地址
3(int *)malloc()函数返回类型使void *类型,表示未确定类型的指针,该类型可强制转换为任何其他类型指针,此处我们需要强转整型,所以是(int *)

int *p; //定义指针p
p = (int *)malloc(sizeof(int));

malloc()函数动态申请空间:

#include
#include //malloc()
using namespace std;
int main()
{
    int *p; //定义指针p
    //指针p获取动态分配的内存地址
    p = (int *)malloc(sizeof(int));
    *p = 10; //向p所指向的内存存入10
    cout<<*p<<endl; //输出p指向内存中的值
    return 0;
}

每一个节点由两部分组成,左边部分存放具体数值,右边部分存储下一个节点的地址(称为后继指针),这里我们定义一个结构体类型来存储这个节点
在这里插入图片描述
详细去看我的数据结构博客【数据结构】万字深入浅出讲解单链表(附原码 | 超详解)

问题:建立链表

#include
#include //malloc()
using namespace std;
struct node
{
    int data;
    struct node *next;
};
int main()
{
    struct node *head, *p, *q, *t;
    int i, n, a;
    cin>>n;
    head = NULL; //头指针初始为空
    for(i = 1; i <= n; ++i) {
        cin>>a;
        //动态申请一个空间存放节点,临时指针p指向这个节点
        p = (struct node *)malloc(sizeof(struct node));
        p->data = a; //数据存储到数据域
        p->next = NULL; //后继指针指向空
        if(head == NULL)
            head = p; //第一个节点让头指针指向
        else
            q->next = p; //否则上一节点后继指针指向当前节点
        q = p; //上一节点指针指向当前节点
    }
 
    //输出链表中所有数
    t = head;
    while(t != NULL) {
        cout<<t->data<<" ";
        t = t->next; //继续下一节点
    }
    return 0;
}

问题:插入数据

#include
#include //malloc()
using namespace std;
 
//创建一个结构体表示节点
struct node
{
    int data;
    struct node *next;
};
 
int main()
{
    struct node *head, *p, *q, *t; //p,q,t都是临时指针
    int i, n, a;
    cin>>n; //n个数
    head = NULL; //头指针初始为空
    for(i = 1; i <= n; ++i) {
        cin>>a;
        //动态申请空间存放一个节点,临时指针p指向该节点
        p = (struct node *)malloc(sizeof(struct node));
        p->data = a;
        p->next = NULL; //当前节点下一节点为空
        if(head == NULL)
            head = p; //若为第一个创建的,头指针指向该节点
        else
            q->next = p; //上一节点后继指针指向当前节点
        q = p; //指针q指向当前节点
    }
 
    cin>>a; //待插入的数
    t = head; //从链表头部开始遍历
    while(t != NULL) {
        if(t->next == NULL || t->next->data > a) {
            p = (struct node *)malloc(sizeof(struct node));
            p->data = a;
            //新增节点后继指针指向当前节点后继指针所指向的节点
            p->next = t->next;
            t->next = p; //当前节点后继指针指向新增节点
            break; //插入完退出循环
        }
        t = t->next; //继续下一节点
    }
 
    //输出链表所有数
    t= head;
    while(t != NULL) {
        cout<<t->data<<" ";
        t = t->next; //继续下一节点
    }
    return 0;
}

总结

先到这儿后面继续补充

你可能感兴趣的:(啊哈算法,算法,链表,数据结构)