9.25 数据结构-二叉树 排序 查找算法总结

时间:2024-10-01 07:54:03

一、树形结构

(1) 树是n个元素的有限集合

(2) n==0 空树

(3) n>0 有且只有一个根结点

① 其他的结点 互不相交的子集

② 树具有递归性:树中有树

 1.1二叉树

概念:每个节点最多拥有两个子树,并且严格区分左右子树的树形结构

1.2二叉树的基本形态 

二叉树的基本形态一共有五种:空树(没有任何节点)、只有根结点、只有根结点和左子树、只有根结点和右子树、根结点和左右子树都有

1.3特殊的二叉树

1> 左斜树:只有左孩子节点

2> 右斜树:只有右孩子节点

3> 满二叉树:在不增加层次的基础上,不能再向该树上增加节点

4> 完全二叉树:在一个满二叉树的基础上,从左向右依次增加结点的二叉树叫完全二叉树

1.4二叉树的性质

1> 在一个二叉树的第k层上,最多拥有2^(k-1)个节点,最少1个

2> 前k层最多有(2^k) -1个节点,最少k个

3> 在二叉树的第n个节点处,如果该节点有左孩子,那么该左孩子是该树的第2*n个节点,如果有右孩子,那么是第2*n+1个节点

4> 在任意一棵二叉树上,叶子节点的个数总比度为2的节点数目多1

n0 = n2 + 1

假设:n0表示度数为0的节点个数,n1表示度数为1的节点个数,n2表示度数为2的节点个数
总结点数 = 树杈数 + 1 = 0*n0 + 1*n1 + 2*n2 + 1

总结点数 = n0+n1+n2
0*n0 + 1*n1 + 2*n2 + 1  ==  n0+n1+n2
 n2 + 1  ==  n0

n0 =  n2 +1  :叶子节点的个数是度为2的节点个数+1

5> 已知结点总个数n个,求深度/高度  [log2n]+1   []向下取整

1.5二叉树的链式存储 

双亲表示法——找双亲方便

孩子兄弟表示法——找孩子兄弟方便

孩子表示法——找孩子方便

1.5.1节点结构体类型
typedef struct node{
    int data;        //数据域
    struct node *left;    //左孩子指针域
    struct node *right;    //右孩子指针域
}tree, *treePtr;
1.5.2创建二叉树 

函数功能:创建一个节点,存储数据,然后给左右子孩子指针创建一棵二叉树。同思路

函数返回值:成功返回树的地址,失败返回NULL

函数名:符合命名规则

参数列表:无

treePtr create(){
    char e;            //定义结点值
    scanf("%c", &e);    //输入结点值
    if(e=='#'){        //若结点值为#  表示空节点  返回NULL
        return NULL;    
    }
    //否则是非空结点
    //1、开辟空间
    treePtr p = (treePtr)malloc(sizeof(tree));
    if(p==NULL){
        printf("error\n");
        return NULL;    
    }
    //2、给数据域赋值
    p->data = e;    
    //3、给左孩子指针域赋值
    p->left = create();    
    //4、给右孩子指针域赋值
    p->right = create();
    //5、返回创建的结点
    return p;
}

测试数据:AB#D##C#EF##G##
1.5.3先序遍历 

函数功能:每次遍历到节点时,先访问数据域,然后访问左子树,最后访问右子树。 ---> 先根 再左 再右

函数返回值:无

函数名:符合命名规则

参数列表:二叉树

void first_show(treePtr T){
    if(T==NULL){
        return;    
    }
    //根
    printf("%c", T->data);
    //左
    first_show(T->left);
    //右
    first_show(T->right);
}
1.5.4中序遍历 

函数功能:每次访问一个新节点时,先中序遍历其左子树,然后访问根节点的数据,最后访问右子树。 --->左 根 右

函数返回值:无

函数名:符合命名规则

参数列表:二叉树

void mid_show(treePtr T){
    if(T==NULL){
        return;    
    }
    //左
    mid_show(T->left);
    //根
    printf("%c", T->data);
    //右
    mid_show(T->right);
}
1.5.5后序遍历 

函数功能:每次访问一个节点时,先进行后序遍历左子树,然后再后序遍历右子树,最后访问根结点。 ---> 左右根

函数返回值:无

函数名:符合命名规则

参数列表:二叉树

void last_show(treePtr T){
    if(T==NULL){
        return;    
    }
    //左
    last_show(T->left);
    //右
    last_show(T->right);
    //根
    printf("%c", T->data);
}

二、算法

2.1概念

算法就是计算机解决问题的方法或步骤

沃斯提出——面向过程的程序 = 数据结构 + 算法

数据结构是肉体

算法是灵魂

eg:

第一步:打开冰箱门

第二步:放进去大象

第三步:关闭冰箱门

2.2 算法的特性

1> 确定性:算法的每一条语句具有确定的意思,不能模棱两可,不具有二义性

2> 有穷性:在执行一定时间后,有限的步骤内,会自动结束算法

3> 输入:至少要有0个或多个输入【可以没有输入】

4> 输出:至少要有一个或多个输出

5> 可行性:经济可行、社会可行性

2.3 算法设计要求

1> 正确性:对于正确的输入,会给出正确的结果,尽可能少的出现Bug

2> 健壮性:对于错误的输入,要给出合理的处理

switch语句中 default语句

3> 可读性:要求代码要有注释、有缩进、命名要规范

4> 高效率:要求时间复杂度尽可能低 T(n) = O(f(n));

5> 低存储:空间复杂度尽可能低 S(n) = O(f(n));

2.4 算法时间复杂度(T(n))

1> 算法时间复杂度计算公式:T(n) = O(f(n));

T(n):时间复杂度

n:表示问题的规模

f(n) :是问题规模与执行次数之间的函数

O(f(n)):使用O阶记法,记录算法时间复杂度

2> 时间复杂度推导

2.5排序算法

2.5.1概念

1> 定义:将给定的序列,按照关键字进行升序【小-大】或降序【大-小】排列的过程叫做排序

2> 分类:

1、 交换类排序:冒泡排序、快速排序

2、 选择类排序:简单选择排序,堆排序

3、 插入类排序:直接插入排序、折半插入排序、希尔排序

4、 归并排序:二路归并、三路归并。。。

5、 基数排序

2.5.2直接插入排序

1> 定义:每次将待排序序列的第一个元素,放在已排序序列中对应的位置

2> 原理: ① 从第一个元素开始,该元素可以认为已经被排序【有序】

② 取出下一个元素,在有序序列中从后向前扫描【倒序循环】 

③ 如果已排序元素大于新元素,将已排序元素向后移【腾出空位,便于插入】 

④ 重复②③,直到排序完成

void insert_sort(int a[], int len){
    //因为a[0]已经有序,所以从a[1] a[2] a[3]……a[len-1]依次前插排序
    for(int i=1; i<len; i++){    //控制待排序元素
        //1、找个临时变量保存待排元素——因为后移会覆盖元素
        int temp = a[i];
        //2、让待排元素temp与前面的有序序列依次进行比较
        for(int j=i-1; j>=0&&temp<a[j]; j--){ //控制前面有序序列
              a[j+1] = a[j];      
        }
        //3、把待排元素放入腾出的空位
        a[j+1] = temp;
    }
}
2.5.3快速排序

1> 定义:快速排序是在序列元素与选定基准元素比较分割为大小两部分的交换排序

2> 原理:从待排序序列中,选定一个基准,以此为基准,将待排序序列分为大小两个部分,对每个部分,再次选择基准进行上述操作,直到每一部分的元素只有一个,则排序成功

① 假设第一个元素为中轴(支点)

② 从最右边元素和中轴比, 比中轴大,下标减减,前移继续比较,遇到一个比中轴小的放在左边

③ 然后从最左边元素和中轴比,比中轴小,下标加加,后移继续比较,遇到一个比中轴大的放在右边

④ 当左右下标重叠时,放入中轴元素

⑤ 然后以中轴元素为分界点,对左右两边,继续进行快速排序【递归调用】

⑥ 直到完全有序

功能:实现一趟快排,确定中轴下标
参数:数组、头下标、尾下标
返回值:int 中轴下标
int once(int a[] , int low, int high){    //头下标  尾下标
    //1、确定中轴值——假设第一个元素
    int key = a[low];
    //2、循环左右往复确定中轴下标
    while(low<high){
        while(key<=a[high] && low<high){
            high--;        
        }//该循环结束时,key>a[high] 找到小的了,放入左边
        a[low] = a[high];
        while(key>=a[low] && low<high){
            low++;        
        }//该循环结束事,key<a[low] 找到大的了,放入右边
        a[high] = a[low];
    }
    //3、把中轴值放入找到的low==high的位置
    a[low] = key;    //a[high] = key;
    //4、返回中轴下标
    return low;      //return high;
}

//以中轴划分左右子序列,递归快排
void quick_sort(int a[], int low, int high){
    if(low<high){
        //1、调用函数,找到中轴
        int mid = once(a, low, high);
        //2、以中轴划分左右子序列,递归快排   
        quick_sort(a, low, mid-1);
        quick_sort(a, mid+1, high);
    }
}

 2.6查找算法

2.6.1概念

按照关键字,在查找表中查询是否存在的算法叫做查找

2.6.2 分类

顺序查找:将给定的查找表进行全部遍历一遍,与要查找的关键字进行比对。

折半查找(二分查找):在顺序存储的有序序列中,通过进行逐次减半查找范围,查找特定的关键字的算法,叫做折半查找。

哈希查找:通过哈希函数,在哈希表中定位要找的数据元素

2.6.3折半查找

1> 要求:查找表是顺序存储,并且有序序列

2> 原理:通过不断减半查找范围,最后确定查找的值

3> 算法

int binary(Plist L,int key)//记录有序才能使用二分查找
{
    int i = 0,j= L->len-1,mid;
    while(i<=j)
    {
        mid = (i+j)/2;
        if(key>L->data[mid].id)//比中间大往右找
        {
            i = mid+1;
        }
        else if(key<L->data[mid].id)//比中间值小往左找
        {
            j = mid-1;
        }
        else//相等
        {
            return mid;//返回下标
        }
    }
2.6.4哈希查找

1> 哈希表是借助哈希函数将序列存储于连续存储空间的查找表

2> 哈希函数是根据关键字确定存储位置的函数

3> 哈希函数的构造方式:直接定址法、数字分析法、平方取中法、折叠法、除留余数法、随机数法

4> 哈希冲突是不同关键字由哈希函数得到相同存储位置的现象

5> 解决哈希冲突的方法:

1、开放定址法

     线性探测法

     二次探测法

     伪随机探测法

2、再哈希法

3、链地址法 (顺序表和链表的集合)

4、建立公共溢出区

6> 哈希表的长度确定方法:数据元素个数除以四分之三得到的最大素数

 代码实现

 hash.c

#include "hash.h"
//初始化数组
void init_hash(Ptrhash hash[]){
	for(int i=0;i<N;i++){
		hash[i]=NULL;
	}
}
//创建哈希表
int insert_hash(Ptrhash hash[],int value){
	Ptrhash p=(Ptrhash)malloc(sizeof(hashnode));
	if(NULL==p){
		printf("空间开辟失败\n");
		return -1;
	}
	p->data=value;
	//计算索引值
	int index=value%N;
	//插入对应索引值的指针后
	p->next=hash[index];
	hash[index]=p;
	return 0;
}
//输出哈希表
int output_hash(Ptrhash hash[]){
	int i;
	for(i=0;i<N;i++){
		printf("%d ",i);
		Ptrhash t=hash[i];
		while(t!=NULL){
			printf("%4d-->",t->data);
			t=t->next;
		}
		printf("NULL\n");
	}
	return 0;
}
//哈希查找
int find_hash(Ptrhash hash[],int value){
	int index=value%N;//计算索引值
	Ptrhash t=hash[index];//按照索引值查找
	int sub=0;
	while(t!=NULL){
		if(t->data==value){
			sub=1;
			break;
		}
		t=t->next;
	}
	if(sub==1){
		printf("找到了\n");
	}else{
		printf("没有该元素\n");
	}
}

hash.h

#ifndef __HASH__
#define __HASH__
#include <myhead.h>
#define N 13
typedef struct node{
	int data;
	struct node *next;
}hashnode,*Ptrhash;
void init_hash(Ptrhash hash[]);
int insert_hash(Ptrhash hash[],int value);
int output_hash(Ptrhash hash[]);
int find_hash(Ptrhash hash[],int value);

#endif

 main.c

#include "hash.h"
int main(int argc, const char *argv[])
{
	int a[]={25,51,8,22,26,67,11,16,54,41};
	int len=sizeof(a)/sizeof(int);
	//采用链地址法解决冲突
	//数组+链表
	//
	int i;
	Ptrhash hash[N];
	init_hash(hash);
	for(i=0;i<len;i++){
		insert_hash(hash,a[i]);
	}
	output_hash(hash);
	find_hash(hash,22);
	find_hash(hash,78);
	return 0;

}