目录
队的概念:
所谓队,是和栈存储方式相反的另外一种存储方式,栈的存储方式特点是先进后出,后进的先出,就如同一个死胡同里,排队人们排队进入,最里面那个人会被后面进来的人挤到栈底出不去,只有外面的人一个一个全出了死胡同,最里面那个人才能出的去。而相对于队的概念,他就像一个通道,规定好了一边出去,另外一个边是出口,队的特点是先进的先出,后进的后出。
顺序队、链式队概念:
顺序队和链式队,简单来说就是利用顺序表或者单向链表的方法来实现队列先进先出的特点,在顺序队中,首先定义两个指针,head和tail,把head作为队头指针,tail作为队尾指针,要实现他们指向谁,谁就得出队或者入队。
在顺序表中,先初始化让head和tail都指向-1,然后入队操作需要:
1.判断是否队满
2.队尾指针+1
3.将数据存入队尾指针指向的位置
出队操作:
1.判断队列是否为空
2.队头指针+1
3.将队头指针指向的数据出队
在链表中,初始化让head和tail两个指针指向头节点,入队操作需要
1.将新节点放入到 队尾指针节点 的后面
2.将队尾指针指向新的节点
出队操作需要
1.判断当前的队列是否为空
2.保存当前 队头指针指向的节点
3.队头指针 往后移动一个
4.将队头指针指向的节点的数据做 出队操作
5.将保存的节点释放
(相关代码在后面)
顺序队(code):
queue.c
#include "queue.h"
#include <stdlib.h>
/**
***********************************
*@brief 初始化
*@param p : 队列
*@retval None
***********************************
*/
void queue_init(pqueue_t *p)
{
*p = (pqueue_t)malloc(sizeof(queue_t));
if(*p==NULL){
perror("malloc error");
return;
}
(*p)->head = -1;
(*p)->tail = -1;
}
/**
***********************************
*@brief 入队
*@param p : 队列
*@param d : 需要入队的数据
*@retval None
***********************************
*/
int enqueue(pqueue_t p,datatype d)
{
//判断是否队满
if(p->tail==N-1){
printf("sorry!队满\n");
return -1;
}
//队尾指针+1
p->tail++;
//将数据存入队尾指针指向的位置
p->data[p->tail]=d;
return 0;
}
/**
***********************************
*@brief 出队
*@param p : 队列
*@param d : 数据
*@retval None
***********************************
*/
int dequeue(pqueue_t p,datatype *d)
{
//判断队列是否为空
if(p->head == p->tail){
printf("sorry!队空\n");
return -1;
}
//队头指针+1
p->head++;
//将队头指针指向的数据出队
*d = p->data[p->head];
return 0;
}
/**
***********************************
*@brief 遍历
*@param p : 队列
*@param d : 数据
*@retval None
***********************************
*/
void display(pqueue_t p)
{
int i;
printf("遍历结果为:\n");
for(i=p->head+1;i<=p->tail;i++)
printf("| %d |\n",p->data[i]);
printf("\n");
}
queue.h
#ifndef __QUEUE_H
#define __QUEUE_H
#include <stdio.h>
#define N 8
typedef int datatype;
typedef struct queue{
datatype data[N];
int head;
int tail;
} queue_t,*pqueue_t;
extern void queue_init(pqueue_t *p);
extern void display(pqueue_t p);
extern int dequeue(pqueue_t p,datatype *d);
extern int enqueue(pqueue_t p,datatype d);
#endif
test.c
#include "queue.h"
int main(void)
{
pqueue_t p = NULL;
queue_init(&p);
if(p==NULL)
return -1;
printf("p:%p\n",p);
//正数入队 负数出队
datatype d;
int ret;
while(1){
ret = scanf("%d",&d);
if(!ret)
break;
if(d>0){
enqueue(p,d);
}else if(d<0){
if(!dequeue(p,&d))
printf("%d出队了\n",d);
}
display(p);
}
return 0;
}
Makefile
app:queue.c test.c queue.h
gcc $^ -o $@ -Wall -O0 -g
链式队:
queue.c
#include "queue.h"
#include <stdlib.h>
/**
***********************************
*@brief 初始化
*@param p : 队列
*@retval None
***********************************
*/
pHT_t queue_init(void)
{
//单向链表头节点开空间初始化
plink_t p = (plink_t)malloc(sizeof(link_t));
if(p==NULL){
perror("malloc plink error");
return NULL;
}
p->next = NULL;
//队头队尾节点开空间初始化
pHT_t node = (pHT_t)malloc(sizeof(HT_t));
if(node==NULL){
perror("malloc pHT_t error");
return NULL;
}
node->head = p;
node->tail = p;
return node;
}
/**
***********************************
*@brief 入队
*@param p : 队列
*@retval None
***********************************
*/
int enqueue(pHT_t p,datatype d)
{
//创建新的节点
plink_t node = (plink_t)malloc(sizeof(link_t));
if(node==NULL){
perror("malloc plink error");
return -1;
}
node->data = d;
node->next = NULL;
//将新节点放入到 队尾指针节点 的后面
p->tail->next = node;
//将队尾指针指向新的节点
p->tail = node;
return 0;
}
/**
***********************************
*@brief 出队
*@param p : 队列
*@retval None
***********************************
*/
int dequeue(pHT_t p,datatype *d)
{
//判断当前的队列是否为空
if(p->head == p->tail){
printf("sorry!队空\n");
return -1;
}
//保存当前 队头指针指向的节点
plink_t node = p->head;
//队头指针 往后移动一个
p->head = p->head->next;
// 将队头指针指向的节点的数据做 出队操作
*d = p->head->data;
// 将保存的节点释放
node->next = NULL;
free(node);
return 0;
}
/**
***********************************
*@brief 遍历
*@param p : 队列
*@retval None
***********************************
*/
void display(pHT_t p)
{
//第一步
plink_t pp = p->head;
printf("遍历结果为:\n");
while(pp!=p->tail){
printf("| %d |\n",pp->next->data);
pp = pp->next;
}
printf("\n");
}
queue.h
#ifndef __QUEUE_H
#define __QUEUE_H
#include <stdio.h>
typedef int datatype;
typedef struct link{
datatype data;
struct link *next;
} link_t,*plink_t;
typedef struct HT{
plink_t head;
plink_t tail;
} HT_t,*pHT_t;
extern pHT_t queue_init(void);
extern int enqueue(pHT_t p,datatype d);
extern int dequeue(pHT_t p,datatype *d);
extern void display(pHT_t p);
#endif
test.c
#include "queue.h"
int main(void)
{
pHT_t p = queue_init();
if(p==NULL)
return -1;
printf("p: %p\n",p);
//正数入队 负数出队
datatype d;
int ret;
while(1){
ret = scanf("%d",&d);
if(!ret)
break;
if(d>0){
enqueue(p,d);
}else if(d<0){
if(!dequeue(p,&d))
printf("%d出队了\n",d);
}
display(p);
}
return 0;
}
(Makefile就不发了,不太懂的话跟着顺序表中的Makefile敲吧)
循环顺序队:
在顺序队中,我们能很明显看到,当数组入队出队至数组最后的时候,既入不了队,也出不了队,就如同一次性的完成入队出队,为了解决这个问题,提出了循环顺序队这个概念,能让入队出队完的垃圾数据还能回收利用。要完成循环的顺序队有三个问题解决,1.什么叫做队满 。 2.什么叫做队空。3.如何循环遍历。
队满是指队头指针指向的位置数据是垃圾值无效数据,其他全部是有效数据,称为队满。队空,是指队头head指向的内容==tail队尾指向的内容,即可称为队空。
循环的关键:关键在于当tail或者head走到数组末端的时候,能进行取余操作,例如数组8个元素,当走到7+1的时候要立刻进行(7+1)%8赋值,并且这种写法还有个BUG,就是head和tail不能和顺序队那样赋-1给他们,这时候必须把0赋给他们,这样(7+1)%8的时候他们会直接指向0,从而达到循环的效果。
循环顺序队(code)
queue.c
#include "queue.h"
#include <stdlib.h>
/**
***********************************
*@brief 初始化
*@param p : 队列
*@retval None
***********************************
*/
void queue_init(pqueue_t *p)
{
*p = (pqueue_t)malloc(sizeof(queue_t));
if(*p==NULL){
perror("malloc error");
return;
}
(*p)->head = 0;
(*p)->tail = 0;
}
/**
***********************************
*@brief 入队
*@param p : 队列
*@param d : 需要入队的数据
*@retval None
***********************************
*/
int enqueue(pqueue_t p,datatype d)
{
//判断是否队满
if((p->tail+1)%N==p->head){
printf("sorry!队满\n");
return -1;
}
//队尾指针+1
p->tail = (p->tail+1)%N;
//将数据存入队尾指针指向的位置
p->data[p->tail]=d;
return 0;
}
/**
***********************************
*@brief 出队
*@param p : 队列
*@param d : 数据
*@retval None
***********************************
*/
int dequeue(pqueue_t p,datatype *d)
{
//判断队列是否为空
if(p->head == p->tail){
printf("sorry!队空\n");
return -1;
}
//队头指针+1
p->head = (p->head+1)%N;
//将队头指针指向的数据出队
*d = p->data[p->head];
return 0;
}
/**
***********************************
*@brief 遍历
*@param p : 队列
*@param d : 数据
*@retval None
***********************************
*/
void display(pqueue_t p)
{
int i;
printf("遍历结果为:\n");
for(i=(p->head+1)%N;i!=(p->tail+1)%N;i=(i+1)%N)
printf("| %d |\n",p->data[i]);
printf("\n");
}
queue.h
#ifndef __QUEUE_H
#define __QUEUE_H
#include <stdio.h>
#define N 8
typedef int datatype;
typedef struct queue{
datatype data[N];
int head;
int tail;
} queue_t,*pqueue_t;
extern void queue_init(pqueue_t *p);
extern void display(pqueue_t p);
extern int dequeue(pqueue_t p,datatype *d);
extern int enqueue(pqueue_t p,datatype d);
#endif
test.c
#include "queue.h"
int main(void)
{
pqueue_t p = NULL;
queue_init(&p);
if(p==NULL)
return -1;
printf("p:%p\n",p);
//正数入队 负数出队
datatype d;
int ret;
while(1){
ret = scanf("%d",&d);
if(!ret)
break;
if(d>0){
enqueue(p,d);
}else if(d<0){
if(!dequeue(p,&d))
printf("%d出队了\n",d);
}
display(p);
}
return 0;
}
二叉树的概念:
树状图相信都不陌生,在我们的思维里,树是有树根的,有树叶的,从结构体->链表->树,他们的一个集合称为树,也称为数据的有限集合,度是衡量每个节点底下有多少个节点,一个节点底下有3个子节点,则可以说该节点是3度的节点,一个树中子节点最多的那个度被称为该树的最大度数。子节点与节点之间的关系,我们称为父子关系。如果一个节点是0度,可以称该节点为叶节点,就如同树叶,他们的底下是没有节点了,属于最外围的一个节点。当多个树放在一起称为森林,即多个树根,若用另外一个树根把森林中的树根集合在一起,森林又会变成树,一颗更大的树。
在这里针对二叉树去学习,二叉树顾名思义,每个节点只有2个子节点的树。二叉树分为有序树和无序树,有序树是其中任意一个节点的位置发生改变,整个二叉树含义就会发生改变,他们的排列是有序列的故名有序树,例如一个节点中左边子节点与右边子节点位置变换一下,整个树的数据集合就会发生改变。无序树同样的道理,他们的排列是没有序列的组合而成的树,称为无序树。
在二叉树中又分为完全二叉树,非完全二叉树,在完全二叉树中又分为满二叉树,和非满二叉树。完全二叉树是指最下面一层的叶节点,左对齐,叶节点之间不能有间隔,可以从右往左一个一个的缺叶节点,但不可以让叶节点之间有间隔。满二叉树,叶节点必须左右两边必须对称一个叶节点都不可以缺。
二叉树的两种存储方式:
二叉树的顺序存储结构
顺序存储结构 :完全二叉树节点的编号方法是从上到下,从左到右,根节点为1号节点。设完全二叉树的节点数为n,某节点编号为i 当i>1(不是根节点)时,有父节点,其编号为i/2」; 当2*i≤n时,有左孩子,其编号为2*i ,否则没有左孩子,本身是叶节点; 当2*i+1≤n时,有右孩子,其编号为2*i+1 ,否则没有右孩子; 当i为奇数且不为1时,有左兄弟,其编号为i-1,否则没有左兄弟; 当i为偶数且小于n时,有右兄弟,其编号为i+1,否则没有右兄弟;
有n个节点的完全二叉树可以用有n+1个元素的数组进行顺序存储,节点号和数组下标一一对应,下标为零的元素不用。 利用以上特性,可以从下标获得节点的逻辑关系。不完全二叉树通过添加虚节点构成完全二叉树,然后用数组存储,这要浪费一些存储空间。
二叉树的链式存储结构
从上面可以看出,顺序二叉树在不完全二叉树下存储数据,需要浪费空间虚构填充数据,那是由于顺序存储他们是一整块一整块的,而链式存储,只要知道地址,就可以把数据利用地址把数据内容相连在一起,数据存储可以碎片化,合理的利用存储空间。
编写顺序二叉树操作:
1.创建一个有数据域、左指针,右指针的结构体
2.创建一个节点
3.将节点数据域赋值
4.将左右指针指向完成1.2.3.4集成的函数,方式和递归一样。
二叉树的建立:
ptree_t create_tree(void)
{
//手动输入
char c;
scanf("%c",&c);
//判断
if(c=='#')
return NULL;
//创建一个节点
ptree_t node = (ptree_t)malloc(sizeof(tree_t));
if(node==NULL){
perror("malloc error");
return NULL;
}
node->data = c;
node->lchild = create_tree();
node->rchild = create_tree();
return node;
}
例题:
给出树的遍历结果,画出相关的树形
1.先找根,遵循先序第一与后序最后相同的字母为根,若不同则为多根;
2.找最左边的字母,中序第一个字母c是最左边的一个节点
3.根据层次和中序靠左,abc.可知,b为c的父节点,a的右分支是NULL
4.再根据中序,和b节点可知,右节点为d,第三层结束
5.再根据中序可知,e为第四层的最左节点,g为第五层最左节点
6.结合可得f为d的右节点
总上所述,图片如下
树的遍历方法:
由于二叉树的递归性质,遍历算法也是递归的。4种基本的遍历算法如下 : 先上后下的按层次遍历;前序遍历法;中序遍历法;后序遍历法;
前序遍历算法:
1.判断若二叉树为空树,则空操作
2.访问根结点
3.先序遍历左子树
4.先序遍历右子树
/**
***********************************
*@brief 先序遍历 : 根左右
*@param p : 根
*@retval None
***********************************
*/
void pre_display(ptree_t p)
{
if(p!=NULL){
printf("%c",p->data);
pre_display(p->lchild);
pre_display(p->rchild);
}
}
中序遍历算法:
1.判断若二叉树为空树,则空操作
2.中序遍历左子树
3.访问根结点
4.中序遍历右子树
/**
***********************************
*@brief 中序遍历 : 左根右
*@param p : 根
*@retval None
***********************************
*/
void mid_display(ptree_t p)
{
if(p!=NULL){
mid_display(p->lchild);
printf("%c",p->data);
mid_display(p->rchild);
}
}
后序遍历算法:
1.判断若二叉树为空树,则空操作
2.后序遍历左子树
3.后序遍历右子树
4.访问根结点
/**
***********************************
*@brief 后序遍历 : 左右根
*@param p : 根
*@retval None
***********************************
*/
void las_display(ptree_t p)
{
if(p!=NULL){
las_display(p->lchild);
las_display(p->rchild);
printf("%c",p->data);
}
}
按层遍历算法:
1.新建一个链式队并初始化
2.创建一个树
3.将根做入队操作
4.链式队的遍历直到队头==队尾
按层遍历(code)
test.c
#include "queue.h"
int main(void)
{
//队列初始化
pHT_t p = queue_init();
if(p==NULL)
return -1;
printf("p: %p\n",p);
//创建树
ptree_t q = create_tree();
if(q==NULL)
return -1;
printf("%p\n",q);
printf("先序遍历的结果为:");
pre_display(q);
printf("\n");
printf("中序遍历的结果为:");
mid_display(q);
printf("\n");
printf("后序遍历的结果为:");
las_display(q);
printf("\n");
//将根做入队操作
enqueue(p,q);
ptree_t node = NULL;
printf("按层遍历的结果为:");
while(p->head != p->tail){
dequeue(p,&node);
printf("%c",node->data);
if(node->lchild!=NULL)
enqueue(p,node->lchild);
if(node->rchild!=NULL)
enqueue(p,node->rchild);
}
printf("\n");
return 0;
}
tree.c
#include "tree.h"
#include <stdlib.h>
/**
***********************************
*@brief 创建树
*@param p : 队列
*@retval None
***********************************
*/
ptree_t create_tree(void)
{
//手动输入
char c;
scanf("%c",&c);
//判断
if(c=='#')
return NULL;
//创建一个节点
ptree_t node = (ptree_t)malloc(sizeof(tree_t));
if(node==NULL){
perror("malloc error");
return NULL;
}
node->data = c;
node->lchild = create_tree();
node->rchild = create_tree();
return node;
}
/**
***********************************
*@brief 先序遍历 : 根左右
*@param p : 根
*@retval None
***********************************
*/
void pre_display(ptree_t p)
{
if(p!=NULL){
printf("%c",p->data);
pre_display(p->lchild);
pre_display(p->rchild);
}
}
/**
***********************************
*@brief 中序遍历 : 左根右
*@param p : 根
*@retval None
***********************************
*/
void mid_display(ptree_t p)
{
if(p!=NULL){
mid_display(p->lchild);
printf("%c",p->data);
mid_display(p->rchild);
}
}
/**
***********************************
*@brief 后序遍历 : 左右根
*@param p : 根
*@retval None
***********************************
*/
void las_display(ptree_t p)
{
if(p!=NULL){
las_display(p->lchild);
las_display(p->rchild);
printf("%c",p->data);
}
}
tree.h
#ifndef __TREE_H
#define __TREE_H
#include <stdio.h>
typedef char datatype2;
typedef struct tree{
datatype2 data;
struct tree *lchild;
struct tree *rchild;
} tree_t,*ptree_t;
extern ptree_t create_tree(void);
extern void pre_display(ptree_t p);
extern void mid_display(ptree_t p);
extern void las_display(ptree_t p);
#endif
queue.c
#include "queue.h"
#include <stdlib.h>
/**
***********************************
*@brief 初始化
*@param p : 队列
*@retval None
***********************************
*/
void queue_init(pqueue_t *p)
{
*p = (pqueue_t)malloc(sizeof(queue_t));
if(*p==NULL){
perror("malloc error");
return;
}
(*p)->head = 0;
(*p)->tail = 0;
}
/**
***********************************
*@brief 入队
*@param p : 队列
*@param d : 需要入队的数据
*@retval None
***********************************
*/
int enqueue(pqueue_t p,datatype d)
{
//判断是否队满
if((p->tail+1)%N==p->head){
printf("sorry!队满\n");
return -1;
}
//队尾指针+1
p->tail = (p->tail+1)%N;
//将数据存入队尾指针指向的位置
p->data[p->tail]=d;
return 0;
}
/**
***********************************
*@brief 出队
*@param p : 队列
*@param d : 数据
*@retval None
***********************************
*/
int dequeue(pqueue_t p,datatype *d)
{
//判断队列是否为空
if(p->head == p->tail){
printf("sorry!队空\n");
return -1;
}
//队头指针+1
p->head = (p->head+1)%N;
//将队头指针指向的数据出队
*d = p->data[p->head];
return 0;
}
/**
***********************************
*@brief 遍历
*@param p : 队列
*@param d : 数据
*@retval None
***********************************
*/
void display(pqueue_t p)
{
int i;
printf("遍历结果为:\n");
for(i=(p->head+1)%N;i!=(p->tail+1)%N;i=(i+1)%N)
printf("| %d |\n",p->data[i]);
printf("\n");
}
queue.h
#ifndef __QUEUE_H
#define __QUEUE_H
#include <stdio.h>
#define N 8
typedef int datatype;
typedef struct queue{
datatype data[N];
int head;
int tail;
} queue_t,*pqueue_t;
extern void queue_init(pqueue_t *p);
extern void display(pqueue_t p);
extern int dequeue(pqueue_t p,datatype *d);
extern int enqueue(pqueue_t p,datatype d);
#endif