一、树形结构相关概念
1> 树形结构:表示数据元素之间存在一对多的关系
2> 树:是由一个根结点个多个子树构成的树形结构
3> 节点:就是树中的数据元素
4> 父亲结点:当前结点的直接上级节点
5> 孩子节点:当前结点的直接下级节点
6> 祖先结点:当前结点的直接或间接上级节点
7> 子孙节点:当前结点的直接或间接下级节点
8> 兄弟节点:拥有相同父结点的所有节点互称为兄弟节点
9> 堂兄弟节点:其父结点在同一层的所有节点,互为堂兄弟节点
10> 根结点:没有父结点的节点
11> 叶子节点:没有子节点的节点称为叶子节点
12> 分支节点:节点的度不为0的节点叫分支节点
13> 节点的度:就是当前结点的孩子节点个数,就称为节点的度
14> 树的度:就是树中节点的度的最大值
15> 节点的层次:从根结点开始到当前结点所经历的层数称为该节点的层次
16> 树的层次:输出节点的层次的最大值
二、二叉树
2.1 二叉树的相关概念
1> 二叉树:由根结点和最多两个子树组成,并且严格区分左右子树的树形结构
2> 左子树:由当前结点的左孩子节点为根结点构成的二叉树
3> 右子树:由当前结点的右孩子节点为根结点构成的二叉树
4> 满二叉树:二叉树的最后一层全是叶子节点,在没有添加层数的条件下,不能在向该树中增加节点的树
(除了最后一层为叶子节点外,其余层中的节点的度全为2)
5> 完全二叉树:在一棵满二叉树的基础上,最后一层自右向左逐渐减少节点的二叉树
2.2 二叉树的状态
一共有五种:空二叉树、只有根结点、只有根结点和左孩子、只有根结点和右孩子、全都有
2.3 二叉树的性质
1> 在二叉树的第 i 层上最多有 2^(i-1)个节点
2> 在二叉树的前n层最多有 2^n - 1个节点
3> 在二叉树中,叶子节点的个数,总比度为2的节点个数多 1
假设:
n0:表示度为0的节点总个数
n1:表示度为1的节点总个数
n2:表示度为2的节点总个数
总度数 = 总结点数据 -1;
总度数 = 0*n0 + 1*n1 + 2*n2;
总结点 = n0 + n1 + n2;
_____________________________________
n2 = n0 - 1;
4> 在二叉树上,如果第i个节点存在左孩子,那么其左孩子一定是第 2*i个节点,如果存在右孩子,那么一定是第2*i+1个节点
2.4 二叉树的存储
1> 顺序存储
对于普通的二叉树而言,如果使用顺序存储,会浪费大量的存储空间,因为需要给没有节点的位置留出空间,以便于后期的插入
顺序存储,一般用于存储完全二叉树,不适合存储普通的二叉树
2> 链式存储
2.5 二叉树的创建
#include"bitree.h"
//创建二叉树
BiTreePtr tree_create()
{
char data = 0;
scanf(" %c", &data); //数据元素值
if(data == '#')
{
return NULL;
}
//如果不是NULL,需要申请结点
BiTreePtr p = (BiTreePtr)malloc(sizeof(Node));
if(NULL==p)
{
printf("结点申请失败\n");
return NULL;
}
//将数据元素放入数据域中
p->data = data;
//递归创建左子树
p->left_child = tree_create();
//递归创建右子树
p->right_child = tree_create();
return p; //将创建好的二叉树返回
}
2.6 先序遍历
//先序遍历
void prio_order(BiTreePtr B)
{
//判断逻辑
if(NULL == B)
{
return; //递归出口
}
//输出数据域
printf("%c\t", B->data);
//先序遍历左子树
prio_order(B->left_child);
//先序遍历右子树
prio_order(B->right_child);
}
2.7 中序遍历
//中序遍历
void in_order(BiTreePtr B)
{
//判断逻辑
if(NULL == B)
{
return; //递归出口
}
//中序遍历左子树
in_order(B->left_child);
//输出数据域
printf("%c\t", B->data);
//中序遍历右子树
in_order(B->right_child);
}
2.8 后序遍历
//后序遍历
void post_order(BiTreePtr B)
{
//判断逻辑
if(NULL == B)
{
return; //递归出口
}
//后序遍历左子树
post_order(B->left_child);
//后序遍历右子树
post_order(B->right_child);
//输出数据域
printf("%c\t", B->data);
}
2.10 考核方式:先序后序定顺序,中序遍历分左右
1> 根据访问序列顺序,画出二叉树
已知一棵二叉树的先序序列 A B D E C F G 和中序序列 D B E A F G C ,画出二叉树。
2> 给出其中两种序列访问方式,写出第三种序列访问方式
一、算法的相关概念
程序 = 数据结构 + 算法
算法是程序设计的灵魂,结构是程序设计的肉体
算法:计算机解决问题的方法或步骤
1.1 算法的特性
1> 确定性:算法中每一条语句都有确定的含义,不能模棱两可
2> 有穷性:程序执行一段时间后会自动结束
3> 输入:至少有零个或多个输入
4> 输出:至少一个或多个输出
5> 可行性:经济可行性、社会可行性、能够运行
1.2 算法的设计要求
1> 正确性:给定合理的输入数据,能够得到正确的结果
2> 健壮性:对于给定的不合理的输入数据,能够给出相应的处理措施
3> 可读性:程序核心代码写注释、程序代码有缩进、程序代码命名规范
4> 高效率:要求时间复杂度要尽可能低
5> 低存储:要求空间复杂度尽可能低
1.3 时间复杂度
1> 算法时间复杂度计算公式:T(n) = O(f(n));
T(n):时间复杂度
n:表示问题的规模
f(n) :是问题规模与执行次数之间的函数
O(f(n)):使用O阶记法,记录算法时间复杂度
2> 时间复杂度推导
3> 常见的时间复杂度
二、排序算法
根据数据元素的关键字,安照升序或降序的方式将数据元素重新排列的过程称为排序
2.1 排序的分类
1> 交换类排序:冒泡排序、快速排序
2> 选择类排序:简单选择排序、堆排序
3> 插入类排序:直接插入排序、折半插入排序
4> 归并排序:二路归并、多路归并
2.2 冒泡排序(O(N^2))
1> 在排序过程中,越大(小)的数据,经由交换后,会慢慢的“浮”到顶端,如同气泡一样
2> 冒泡排序原理
比较相邻元素,如果第一个比第二个大(小)则交换
经过一趟排序后会使最大(最小)的元素落到最后 重复上面的步骤,直到没有任何一对数字需要比较为止
当某一趟的排序过程中,出现没有数据交换的过程,则结束整个排序
//定义冒泡排序函数
void bubble_sort(int *arr, int n)
{
for(int i=1; i<n; i++) //趟数
{
int flag = 0; //判断是否在排序中改变
for(int j=0; j<n-i; j++)
{
if(arr[j] > arr[j+1])
{
flag = 1; //设置标志
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
//说明上一堂的排序中,没有进行数据的交换
if(flag == 0)
{
break;
}
}
printf("排序成功\n");
}
2.3 选择排序
1> 概念:每次从待排序序列中,找到最大(小)值,将其与待排序序列的第一个进行交换
2> 原理
1、从待排序序列中选择最值
2、如果最值不是待排序序列的第一个,则进行交换
3、从剩余待排序序列中,继续重复前两次的操作,直到,待排序序列为空
3> 算法
//定义选择排序
void select_sort(int *arr, int n)
{
int mini = 0;
for(int i=0; i<n; i++) //遍历已排序序列
{
mini = i; //将待排序的第一个元素当做最值
for(int j=i+1; j<n; j++) //遍历待排序序列
{
if(arr[mini] > arr[j])
{
mini = j; //更新最小值下标
}
}
//判断最小值是否是第一个元素
if(mini != i)
{
//将最小值与待排序序列第一个交换
int temp = arr[mini];
arr[mini] = arr[i];
arr[i] = temp;
}
}
printf("排序结果\n");
}
2.4 直接插入排序
1> 每次从待排序序列中,选择第一个,将其插入到已排序序列中
2> 原理
1、选取待排序序列中的第一个元素
2、跟前面的元素依次比较,如果前面的比当前元素大(小),则将前面的元素后移一位
3、直到出现前面的不比当前的大(小)或者已经到最前面了,将选取的元素,放入空位置上
4、对于待排序序列中的所有元素,重复上述操作
3> 代码
//定义插入排序函数
void insert_sort(int *arr, int n)
{
int i,j;
for(i=1; i<n; i++) //不断从待排序序列中选元素
{
int temp = arr[i]; //将待排序序列的第一个备份
for(j=i-1; temp<=arr[j]&&j>=0; j--)
{
//将元素后移
arr[j+1] = arr[j];
}
arr[j+1] = temp; //将元素放入对应位置
}
printf("排序成功\n");
}
2.5 快速排序(O(n*log2n))
1> 概念:快速排序是在序列元素与选定基准元素比较分割为大小两部分的交换排序
2> 原理
从待排序列中任取一个基准元素
与基准元素比较将待排序列分割为大小两部分
再对各部分重新选择基准元素并依此规则排序
直到每个部分只剩一个元素为止
3> 算法
//定义一趟快速排序函数
//返回值:基准最终的下标
//arr:数组起始地址
//low:要排序容器的最小下标
//high:要排序容器的最大下标
int part(int *arr, int low, int high)
{
//选中基准
int X = arr[low]; //把第一个元素当做基准
while(high > low) //让循环继续的条件
{
//判断high所在的元素是否都比基准大
while(arr[high] >= X && high>low) //为了不错位
{
high--;
}
arr[low] = arr[high]; //将小的值,向前放
//判断low所在的元素是否都比基准小
while(arr[low] <= X && high>low)
{
low++;
}
arr[high] = arr[low]; //将大的值,向后放
}
//基准的位置就选出来了 此时 high==low
arr[low] = X; //将基准放入指定位置
printf("排序一趟\n");
return low; //返回基准的下标
}
//定义快速排序函数
void quick_sort(int *arr, int low, int high)
{
if(low>=high)
{
return; //只有一个元素,无需排序。递归出口
}
//不只一个元素时
int mid = part(arr, low, high); //进行一趟排序
//对左半部分快排
quick_sort(arr, low, mid-1);
//对右半部分快排
quick_sort(arr, mid+1, high);
}