Day26.C提高(数据结构03)
01、队列概念
队列是一种受限制的线性表(先进先出)
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出的线性表,允许插入的一端为队尾,允许删除的一端为队头。
队列不允许在中间部位进行操作!假设队列是q=(a1,a2,a3---an),那么a1就是队头元素,而an为队尾元素,这样我们就可以删除时,总是在a1开始,而插入时,总是在队列最后。
02、队列的链式存储实现
LinkQueue.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#ifdef __cplusplus
extern "C" {
#endif
//链表结点的数据类型
struct QueueNode
{
struct QueueNode* next;
};
//链表数据类型
struct LQueue
{
struct QueueNode header;//头结点
int size;
struct QueueNode* rear;//尾指针,始终指向链表尾部最后一个结点
};
typedef void* LinkQueue;
//初始化
LinkQueue Init_LinkQueue();
//入队
void Push_LinkQueue(LinkQueue queue, void* data);
//出队
void Pop_LinkQueue(LinkQueue queue);
//获得队头元素
void* Front_LinkQueue(LinkQueue queue);
//获得队尾元素
void* Back_LinkQueue(LinkQueue queue);
//大小
int Size_LinkQueue(LinkQueue queue);
//销毁队列
void Destroy_LinkQueue(LinkQueue queue);
#ifdef __cplusplus
}
#endif
LinkQueue.c
#include"LinkQueue.h"
//初始化
LinkQueue Init_LinkQueue()
{
struct LQueue* queue = malloc(sizeof(struct LQueue));
if (NULL == queue)
{
return NULL;
}
queue->header.next = NULL;
queue->size = 0;
queue->rear = &(queue->header);
return queue;
}
//入队
void Push_LinkQueue(LinkQueue queue, void* data)
{
if (NULL == queue)
{
return;
}
if (NULL == data)
{
return;
}
struct LQueue* myqueue = (struct LQueue*)queue;
struct QueueNode* n = (struct QueueNode*)data;
myqueue->rear->next = n;
n->next = NULL;
//更新尾指针
myqueue->rear = n;
myqueue->size++;
}
//出队
void Pop_LinkQueue(LinkQueue queue)
{
if (NULL == queue)
{
return;
}
struct LQueue* myqueue = (struct LQueue*)queue;
if (myqueue->size == 0)
{
return;
}
if (myqueue->size == 1)
{
myqueue->header.next = NULL;
myqueue->rear = &(myqueue->header);
myqueue->size--;
return;
}
struct QueueNode* qFirstNode = myqueue->header.next;
myqueue->header.next = qFirstNode->next;
myqueue->size--;
}
//获得队头元素
void* Front_LinkQueue(LinkQueue queue)
{
if (NULL == queue)
{
return NULL;
}
struct LQueue* myqueue = (struct LQueue*)queue;
return myqueue->header.next;
}
//获得队尾元素
void* Back_LinkQueue(LinkQueue queue)
{
if (NULL == queue)
{
return NULL;
}
struct LQueue* myqueue = (struct LQueue*)queue;
return myqueue->rear;
}
//大小
int Size_LinkQueue(LinkQueue queue)
{
if (NULL == queue)
{
return -1;
}
struct LQueue* myqueue = (struct LQueue*)queue;
return myqueue->size;
}
//销毁队列
void Destroy_LinkQueue(LinkQueue queue)
{
if (NULL == queue)
{
return;
}
free(queue);
queue = NULL;
}
队列的链式存储.c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"LinkQueue.h"
struct Person_01
{
struct QueueNode node;
char name[64];
int age;
};
void test101()
{
//初始化队列
LinkQueue queue = Init_LinkQueue();
//创建数据
struct Person_01 p1 = { NULL,"aaa",10 };
struct Person_01 p2 = { NULL,"bbb",20 };
struct Person_01 p3 = { NULL,"ccc",30 };
struct Person_01 p4 = { NULL,"ddd",40 };
struct Person_01 p5 = { NULL,"eee",50 };
struct Person_01 p6 = { NULL,"fff",60 };
//获得队尾元素
struct Person_01* pBack = (struct Person_01*)Back_LinkQueue(queue);
printf("队尾元素:%s %d\n", pBack->name, pBack->age);
//插入队列
Push_LinkQueue(queue, &p1);
Push_LinkQueue(queue, &p2);
Push_LinkQueue(queue, &p3);
Push_LinkQueue(queue, &p4);
Push_LinkQueue(queue, &p5);
Push_LinkQueue(queue, &p6);
while (Size_LinkQueue(queue) > 0)
{
//获得队头元素
struct Person_01* person = (struct Person_01*)Front_LinkQueue(queue);
//打印队头元素
printf("Name:%s Age:%d\n", person->name, person->age);
//弹出队头元素
Pop_LinkQueue(queue);
}
//销毁队列
Destroy_LinkQueue(queue);
}
int main(void)
{
test101();
system("pause");
return EXIT_SUCCESS;
}
003.树和二叉树的概念
数的定义:
由一个或多个(n>=0)结点组成的有限集合T,有且仅有一个结点称为根(root),当n>1时,其余的结点分为m(m>=0)个互不相交的有限集合T1,T2,T3···Tm。每个集合本身又是棵树,被称作这个根的子树。
树的结构特点:
非线性结构,有一个直接前驱,但可能有多个直接后继(1:n)。
树的定义具有递归性,树中还有树。
树可以为空,即结点个数为0/
若干术语:
根:即根节点(没有前驱)
叶子:度为0的结点,即终端结点(没有后继)
森林:指m棵不想交的树的集合
有序树:结点各子树从左至右有序,不能互换(左为第一)
无序树:结点各子树可以互换位置
结点:即树的数据元素
结点的度:有几个直接后继,就是几度
结点的层次:从根到该节点的层数(根结点算第一层)
分支结点:除树根以外的结点
树的度:所有结点度中的最大值
完全二叉树
除最后一层外,每一层上的
004.二叉树的遍历
遍历方法:
牢记一个约定,对每个结点的查看都是“先左后右”
DLR LDR LRD
先序遍历 中序遍历 后序遍历
- DLR——>先序遍历,即先根再左再右
- LDR——>中序遍历,先左再根再右
- LRD——>后序遍历,先左再右再根
注:“先 中 后”的意思是指访问的结点D是先于还是后于子树出现,从递归的角度看,这三种算法是完全相同的,或者说这三种遍历算法的访问路径是相同的,只是访问的时机不同。
二叉树的遍历,叶子结点计数,求高度,拷贝,释放(代码示例)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct BiNode
{
char ch;
struct BiNode* lchild;
struct BiNode* rchild;
};
//二叉树递归遍历(先序遍历)
void recursion(struct BiNode* root)
{
if (NULL == root)
{
return;
}
printf("%c ", root->ch);
//递归遍历左子树
recursion(root->lchild);
//递归遍历右子树
recursion(root->rchild);
}
//求二叉树的叶子结点数目
void calculateLeafNum(struct BiNode* root,int* pnum)
{
if (NULL == root)
{
return;
}
if (root->lchild == NULL && root->rchild == NULL)
{
(*pnum)++;
}
calculateLeafNum(root->lchild,pnum);
calculateLeafNum(root->rchild,pnum);
}
//求二叉树的高度
int getTreeHeight(struct BiNode* root)
{
if (NULL == root)
{
return 0;
}
//求树的左子树的高度
int lheight = getTreeHeight(root->lchild);
//求树的右子树的高度
int rheight = getTreeHeight(root->rchild);
int height = lheight > rheight ? lheight + 1 : rheight + 1;
return height;
}
//拷贝二叉树
struct BiNode* CopyBiTree(struct BiNode* root)
{
if (NULL == root)
{
return NULL;
}
//先拷贝左子树
struct BiNode* lchild = CopyBiTree(root->lchild);
//再拷贝右子树
struct BiNode* rchild = CopyBiTree(root->rchild);
struct BiNode* newnode = malloc(sizeof(struct BiNode));
if (NULL == newnode)
{
return NULL;
}
newnode->lchild = lchild;
newnode->rchild = rchild;
newnode->ch = root->ch;
return newnode;
}
//释放拷贝的二叉树
void FreeCopiedBiTree(struct BiNode* root)
{
if (NULL == root)
{
return;
}
//先释放左子树内存
FreeCopiedBiTree(root->lchild);
//再释放右子树内存
FreeCopiedBiTree(root->rchild);
free(root);
root = NULL;
}
void test201()
{
struct BiNode nodeA = { 'A',NULL,NULL };
struct BiNode nodeB = { 'B',NULL,NULL };
struct BiNode nodeC = { 'C',NULL,NULL };
struct BiNode nodeD = { 'D',NULL,NULL };
struct BiNode nodeE = { 'E',NULL,NULL };
struct BiNode nodeF = { 'F',NULL,NULL };
struct BiNode nodeG = { 'G',NULL,NULL };
struct BiNode nodeH = { 'H',NULL,NULL };
nodeA.lchild = &nodeB;
nodeA.rchild = &nodeF;
nodeB.rchild = &nodeC;
nodeC.lchild = &nodeD;
nodeC.rchild = &nodeE;
nodeF.rchild = &nodeG;
nodeG.lchild = &nodeH;
//先序遍历
recursion(&nodeA);
printf("\n");
//1.求二叉树的叶子节点数
int num = 0;
calculateLeafNum(&nodeA,&num);
printf("叶子节点数:%d\n", num);
//2.求二叉树的高度
int height = getTreeHeight(&nodeA);
printf("树的高度:%d\n", height);
//拷贝二叉树
struct BiNode* newnode = CopyBiTree(&nodeA);
//先序遍历拷贝后的二叉树
recursion(newnode);
printf("\n");
//释放拷贝的二叉树
FreeCopiedBiTree(newnode);
}
int main(void)
{
test201();
system("pause");
return EXIT_SUCCESS;
}
005.二叉树的非递归遍历
大致步骤:
1. 将根节点压入栈中,(并且给一个标识符(FALSE))
2. 开始while循环,size>0
2.1 先从栈中弹出栈顶元素,判断元素的标志是TRUE还是FALSE,如果为TRUE,直接输出,本轮循环结束。如果是FALSE,就把当前结点以及左子树和右子树压栈。(第一次压栈的标识符为FALSE,第二次为TRUE)
代码实现:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>
#include"SeqStack.h"
struct BiNode
{
char ch;
struct BiNode* lchild;
struct BiNode* rchild;
};
struct Info
{
struct BiNode* node;
bool flag;
};
struct Info* createInfo(struct BiNode* node, bool flag)
{
struct Info* info = malloc(sizeof(struct Info));
if (NULL == info)
{
return NULL;
}
info->flag = flag;
info->node = node;
return info;
}
void nonRecursion(struct BiNode* root)
{
//初始化栈
SeqStack stack = Init_SeqStack();
//先把根节点压入栈中
Push_SeqStack(stack, createInfo(root, false));
while (Size_SeqStack(stack) > 0)
{
//获得栈顶元素
struct Info* info = (struct Info*)Top_SeqStack(stack);
//弹出栈顶元素
Pop_SeqStack(stack);
//判断标识符
if (info->flag)
{
printf("%c ", info->node->ch);
free(info);
continue;
}
//将右子树压入栈中
if (info->node->rchild != NULL)
{
Push_SeqStack(stack, createInfo(info->node->rchild, false));
}
//将左子树压入栈中
if (info->node->lchild != NULL)
{
Push_SeqStack(stack, createInfo(info->node->lchild, false));
}
//将根节点压入栈中
info->flag = true;
Push_SeqStack(stack, info);
}
//销毁栈
Destroy_SeqStack(stack);
}
void test301()
{
struct BiNode nodeA = { 'A',NULL,NULL };
struct BiNode nodeB = { 'B',NULL,NULL };
struct BiNode nodeC = { 'C',NULL,NULL };
struct BiNode nodeD = { 'D',NULL,NULL };
struct BiNode nodeE = { 'E',NULL,NULL };
struct BiNode nodeF = { 'F',NULL,NULL };
struct BiNode nodeG = { 'G',NULL,NULL };
struct BiNode nodeH = { 'H',NULL,NULL };
nodeA.lchild = &nodeB;
nodeA.rchild = &nodeF;
nodeB.rchild = &nodeC;
nodeC.lchild = &nodeD;
nodeC.rchild = &nodeE;
nodeF.rchild = &nodeG;
nodeG.lchild = &nodeH;
nonRecursion(&nodeA);
}
int main(void)
{
test301();
system("pause");
return EXIT_SUCCESS;
}
006.插入排序
插入排序简单介绍:
插入排序算法是一种简单的排序算法,也称为直接插入排序算法,它是一种稳定的排序算法,对局部有序的数据具有较高的效率
插入排序算法是一个对少量元素进行排序的有效算法。
比如,打牌是我们使用插入排序方法最多的日常生活的例子,在我们摸牌时,一般会重复以下步骤。起初,我们手里没有牌,摸出第一张,随意放在左手上,以后每一次摸牌,都会按照数字由小到大排列,直到所有的牌都摸完。
插入排序算法采用类似的思路,每一次从无序序列中拿出一个数据,将它放在已排序的序列的正确的位置,如此重复,直到所有的无序序列中的数据都找到了正确的位置。
代码实现:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//打印数组
void printfArray(int arr[], int len)
{
for (int i = 0; i < len; ++i)
{
printf("%d ", arr[i]);
}
printf("\n");
}
//插入排序
void insertSort(int arr[],int len)
{
for (int i = 0; i < len; ++i)
{
if (arr[i] < arr[i - 1])
{
int temp = arr[i];
int j = i - 1;
for (; j >= 0 && temp < arr[j]; --j)
{
arr[j + 1] = arr[j];
}
arr[j + 1] = temp;
}
}
}
void test401()
{
int arr[] = { 5,3,9,2,1,3 };
int len = sizeof(arr) / sizeof(arr[0]);
printfArray(arr, len);
insertSort(arr, len);
printfArray(arr, len);
}
int main(void)
{
test401();
system("pause");
return EXIT_SUCCESS;
}