橙色
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
/* 二叉树的二叉链表结点结构定义 */
typedef struct BiTNode /* 结点结构 */
{
int data; /* 结点数据 */
struct BiTNode *lchild, *rchild; /* 左右孩子指针 */
} BiTNode, *BiTree;
/* 递归查找二叉排序树T中是否存在key, */
/* 指针f指向T的双亲,其初始调用值为NULL */
/* 若查找成功,则指针p指向该数据元素结点,并返回TRUE */
/* 否则指针p指向查找路径上访问的最后一个结点并返回FALSE */
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p)
{
if (!T) /* T为空,查找不成功,返回false */
{
*p = f;
return FALSE;
}
else if (key==T->data) /* 查找成功 */
{
*p = T;
return TRUE;
}
else if (key<T->data)
return SearchBST(T->lchild, key, T, p); /* 在左子树中继续查找 */
else
return SearchBST(T->rchild, key, T, p); /* 在右子树中继续查找 */
}
/* 当二叉排序树T中不存在关键字等于key的数据元素时, */
/* 插入key并返回TRUE,否则返回FALSE */
Status InsertBST(BiTree *T, int key)
{
BiTree p,s;
if (!SearchBST(*T, key, NULL, &p)) /* 查找不成功,p是一个传出参数 */
{
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if (!p)
*T = s; /* 插入s为新的根结点 */
else if (key<p->data)
p->lchild = s; /* 插入s为左孩子 */
else
p->rchild = s; /* 插入s为右孩子 */
return TRUE;
}
else
return FALSE; /* 树中已有关键字相同的结点,不再插入 */
}
/* 从二叉排序树中删除结点p,并重接它的左或右子树。 */
Status Delete(BiTree *p)
{
BiTree q,s;
if((*p)->rchild==NULL) /* 右子树空则只需重接它的左子树(待删结点是叶子也走此分支) */
{
q=*p; *p=(*p)->lchild; free(q);
}
else if((*p)->lchild==NULL) /* 只需重接它的右子树 */
{
q=*p; *p=(*p)->rchild; free(q);
}
else /* 左右子树均不空 */
{
q=*p; s=(*p)->lchild;
while(s->rchild) /* 转左,然后向右到尽头(找待删结点的前驱) */
{
q=s;
s=s->rchild;
}
(*p)->data=s->data; /* s指向被删结点的直接前驱(将被删结点前驱的值取代被删结点的值) */
if(q!=*p) //代表p的左节点有右子树,此时s在p的左节点的右子树最右边,不会有右节点了
q->rchild=s->lchild; /* 重接q的右子树 */
else //代表p的左节点没有右子树,此时s就是p的左节点,也是p的前驱。
q->lchild=s->lchild; /* 重接q的左子树 */
free(s);
}
return TRUE;
}
/* 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素结点, */
/* 并返回TRUE;否则返回FALSE。 */
Status DeleteBST(BiTree *T,int key)
{
if(!*T) /* 不存在关键字等于key的数据元素 */
return FALSE;
else
{
if (key==(*T)->data) /* 找到关键字等于key的数据元素 */
return Delete(T);
else if (key<(*T)->data)
return DeleteBST(&(*T)->lchild,key);
else
return DeleteBST(&(*T)->rchild,key);
}
}
void ShowTree(BiTree t){
if(t==NULL){
return;
}
printf("%d ", t->data);
ShowTree(t->lchild);
ShowTree(t->rchild);
}
int main()
{
int i;
int a[10]={62,88,58,47,35,73,51,99,37,93};
BiTree T=NULL;
for(i=0;i<10;i++)
{
InsertBST(&T, a[i]);
}
ShowTree(T);
printf("\n");
DeleteBST(&T,47);
printf("本样例建议断点跟踪查看二叉排序树结构");
printf("\n");
ShowTree(T);
return 0;
}
总之,二叉排序树以链接的方式存储,保持了链接存储结构在执行插入或删除操作时不用移动元素的优点,只要找到合适的插入和删除位置后,仅需修改链接指针即可。插入删除的时间性能比较好。而对于二叉排序树的查找,走的就是从根结点到要查找的结点的路径,其比较次数等于给定值的结点在二叉排序树中的层数。极端情况,最少为1次,即根结点就是要找的结点,最多也不会超过树的深度。也就是说,二叉排序树的查找性能取决于二叉排序树的形状。可问题就在于,二叉排序树的形状是不确定的。
例如{62,88,58,47,35,73,51,99,37,93}这样的数组,我们可以构建如左下图所示的二叉排序树。但如果数组元素的次序是从小到大有序如{35,37,47,51,58,62,73,88,93,99},则二叉排序树就成了极端的右斜树,注意它依然是一棵二叉树,如右下图。此时,同样是查找结点99,左下图只需要比较两次,而右下图需要10次比较才能得到结果,二者差异很大。
也就是说,我们希望二叉排序树是比较平衡的,即其深度与完全二叉树相同,均为(logn)+1,那么查找的时间复杂也就为O(logn),近似于折半查找,事实上,左上图也不够平衡,明显的左重右轻。
不平衡的最坏情况就是像右上图的斜树,查找时间复杂度为O(n),这等同于顺序查找。
因此,如果我们希望对一个集合按二叉排序树查找,最好是把它构建成一棵平衡的二叉排序树。这样我们就引申出另一个问题,如何让二叉排序树平衡的问题。
代码是挺难看懂的,可以结合这篇博客来理解:数据结构之AVL树(平衡二叉树)的理解
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
/* 二叉树的二叉链表结点结构定义 */
typedef struct BiTNode /* 结点结构 */
{
int data; /* 结点数据 */
int bf; /* 结点的平衡因子,即结点的左子树高度减去右子树高度 */
struct BiTNode *lchild, *rchild; /* 左右孩子指针 */
} BiTNode, *BiTree;
/* 对以p为根的二叉排序树作右旋处理, */
/* 处理之后p指向新的树根结点,即旋转处理之前的左子树的根结点 */
void R_Rotate(BiTree *P)
{
BiTree L;
L=(*P)->lchild; /* L指向P的左子树根结点 */
(*P)->lchild=L->rchild; /* L的右子树挂接为P的左子树 */
L->rchild=(*P);
*P=L; /* P指向新的根结点 */
}
/* 对以P为根的二叉排序树作左旋处理, */
/* 处理之后P指向新的树根结点,即旋转处理之前的右子树的根结点0 */
void L_Rotate(BiTree *P)
{
BiTree R;
R=(*P)->rchild; /* R指向P的右子树根结点 */
(*P)->rchild=R->lchild; /* R的左子树挂接为P的右子树 */
R->lchild=(*P);
*P=R; /* P指向新的根结点 */
}
#define LH +1 /* 左高 */
#define EH 0 /* 等高 */
#define RH -1 /* 右高 */
/* 对以指针T所指结点为根的二叉树作左平衡旋转处理 */
/* 本算法结束时,指针T指向新的根结点 */
void LeftBalance(BiTree *T)
{
BiTree L,Lr;
L=(*T)->lchild; /* L指向T的左子树根结点 */
switch(L->bf)
{ /* 检查T的左子树的平衡度,并作相应平衡处理 */
case LH: /* 新结点插入在T的左孩子的左子树上,要作单右旋处理 */
(*T)->bf=L->bf=EH;
R_Rotate(T);
break;
//L的右子树更高,即L的bf与T的bf值符号相反
case RH: /* 新结点插入在T的左孩子的右子树上,要作双旋处理 */
Lr=L->rchild; /* Lr指向T的左孩子的右子树根 */
switch(Lr->bf)
{ /* 修改T及其左孩子的平衡因子 */
case LH: (*T)->bf=RH;
L->bf=EH;
break;
case EH: (*T)->bf=L->bf=EH;
break;
case RH: (*T)->bf=EH;
L->bf=LH;
break;
}
Lr->bf=EH;
L_Rotate(&(*T)->lchild); /* 对T的左子树作左旋平衡处理 */
R_Rotate(T); /* 对T作右旋平衡处理 */
}
}
/* 对以指针T所指结点为根的二叉树作右平衡旋转处理, */
/* 本算法结束时,指针T指向新的根结点 */
void RightBalance(BiTree *T)
{
BiTree R,Rl;
R=(*T)->rchild; /* R指向T的右子树根结点 */
switch(R->bf)
{ /* 检查T的右子树的平衡度,并作相应平衡处理 */
case RH: /* 新结点插入在T的右孩子的右子树上,要作单左旋处理 */
(*T)->bf=R->bf=EH;
L_Rotate(T);
break;
case LH: /* 新结点插入在T的右孩子的左子树上,要作双旋处理 */
Rl=R->lchild; /* Rl指向T的右孩子的左子树根 */
switch(Rl->bf)
{ /* 修改T及其右孩子的平衡因子 */
case RH: (*T)->bf=LH;
R->bf=EH;
break;
case EH: (*T)->bf=R->bf=EH;
break;
case LH: (*T)->bf=EH;
R->bf=RH;
break;
}
Rl->bf=EH;
R_Rotate(&(*T)->rchild); /* 对T的右子树作右旋平衡处理 */
L_Rotate(T); /* 对T作左旋平衡处理 */
}
}
/* 若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个 */
/* 数据元素为e的新结点,并返回1,否则返回0。若因插入而使二叉排序树 */
/* 失去平衡,则作平衡旋转处理,布尔变量taller反映T长高与否。 */
Status InsertAVL(BiTree *T,int e,Status *taller)
{
if(!*T)//如果是一棵空树
{ /* 插入新结点,树“长高”,置taller为TRUE */
*T=(BiTree)malloc(sizeof(BiTNode));
(*T)->data=e; (*T)->lchild=(*T)->rchild=NULL; (*T)->bf=EH;
*taller=TRUE;
}
else
{
if (e==(*T)->data)
{ /* 树中已存在和e有相同关键字的结点则不再插入 */
*taller=FALSE; return FALSE;
}
if (e<(*T)->data)
{ /* 应继续在T的左子树中进行搜索 */
if(!InsertAVL(&(*T)->lchild,e,taller)) /* 返回结果为false,未插入 */
return FALSE;
if(*taller) /* 已插入到T的左子树中且左子树“长高” */
switch((*T)->bf) /* 检查T的平衡度 */
{
case LH: /* 原本左子树比右子树高,需要作左平衡处理 */
LeftBalance(T); *taller=FALSE; break;
case EH: /* 原本左、右子树等高,现因左子树增高而使树增高 */
(*T)->bf=LH; *taller=TRUE; break;
case RH: /* 原本右子树比左子树高,现左、右子树等高 */
(*T)->bf=EH; *taller=FALSE; break;
}
}
else
{ /* 应继续在T的右子树中进行搜索 */
if(!InsertAVL(&(*T)->rchild,e,taller)) /* 未插入 */
return FALSE;
if(*taller) /* 已插入到T的右子树且右子树“长高” */
switch((*T)->bf) /* 检查T的平衡度 */
{
case LH: /* 原本左子树比右子树高,现左、右子树等高 */
(*T)->bf=EH; *taller=FALSE; break;
case EH: /* 原本左、右子树等高,现因右子树增高而使树增高 */
(*T)->bf=RH; *taller=TRUE; break;
case RH: /* 原本右子树比左子树高,需要作右平衡处理 */
RightBalance(T); *taller=FALSE; break;
}
}
}
return TRUE;
}
int main(void)
{
int i;
int a[10]={3,2,1,4,5,6,7,10,9,8};
BiTree T=NULL;
Status taller;
for(i=0;i<10;i++)
{
InsertAVL(&T,a[i],&taller);
}
printf("本样例建议断点跟踪查看平衡二叉树结构");
return 0;
}
在这一段代码中,有初始化、插入和查找三个函数。在本段代码中,key是想要查找的值的键值,H->elem[key]是想要查找的值,想要查找的值的地址为数组的索引值。
#include "stdio.h"
#include "stdlib.h"
#define MAXSIZE 100 /* 存储空间初始分配量 */
#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12 /* 定义散列表长为数组的长度 */
#define NULLKEY -32768
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef struct
{
int *elem; /* 数据元素存储基址,动态分配数组 */
int count; /* 当前数据元素个数 */
}HashTable;
int m=0; /* 散列表表长,全局变量 */
/* 初始化散列表 */
Status InitHashTable(HashTable *H)
{
int i;
m=HASHSIZE;
H->elem = (int *)malloc(sizeof(int) * m);
for (i = 0; i < m;i++){
H->elem[i] = NULLKEY;
}
return SUCCESS;
}
/* 散列函数 */
int Hash(int key)
{
return key % m; /* 除留余数法 */
}
/* 插入关键字进散列表 */
void InsertHash(HashTable *H,int key)
{
int addr = Hash(key);
while(H->elem[addr]!=NULLKEY){
addr = (addr+1)%m;
}
H->elem[addr] = key;
}
/* 散列表查找关键字 */
Status SearchHash(HashTable H,int key,int *addr)
{
*addr = Hash(key); /* 求散列地址 */
while(H.elem[*addr] != key) /* 如果不为空,则冲突 */
{
*addr = (*addr+1) % m; /* 开放定址法的线性探测 */
if (H.elem[*addr] == NULLKEY || *addr == Hash(key)) /* 如果循环回到原点 */
return UNSUCCESS; /* 则说明关键字不存在 */
}
return SUCCESS;
}
int main()
{
int arr[HASHSIZE]={12,67,56,16,25,37,22,29,15,47,48,34};
int i,p,key,result;
HashTable H;
key=39;
InitHashTable(&H);
for(i=0;i<m;i++)
InsertHash(&H,arr[i]);
result=SearchHash(H,key,&p);
if (result)
printf("查找 %d 的地址为:%d \n",key,p);
else
printf("查找 %d 失败。\n",key);
for(i=0;i<m;i++)
{
key=arr[i];
SearchHash(H,key,&p);
printf("查找 %d 的地址为:%d \n",key,p);
}
return 0;
}
第14行的i记录的是已经完成排序的索引值,所以第15行j>=i,此时在i之前的都是已经依次排好的值,没有必要再去拍了
#include
using namespace std;
void swap(int* a,int i,int j){
int temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
int main(){
int a[] = {9,1,5,8,3,7,4,6,2};
int len=sizeof(a)/sizeof(a[0]);
for (int i = 0; i < len;i++){
for (int j = len - 2; j >= i;j--){
if(a[j]>a[j+1]){
swap(a, j, j + 1);
}
}
}
for (int i = 0; i < len;i++){
cout << a[i];
}
return 0;
}
加了一个标记变量,初始化为true。如果在某一次排序中没有发生任何一次交换,即flag始终为false,即说明该数组已经有序,没有必要在进行无意义的循环判断了。
#include
using namespace std;
void swap(int* a,int i,int j){
int temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
int main(){
int a[] = {9,1,5,8,3,7,4,6,2};
int len=sizeof(a)/sizeof(a[0]);
int flag = true;
for (int i = 0; i < len&&true;i++){
flag = false;
for (int j = len - 2; j >= i;j--){
if(a[j]>a[j+1]){
swap(a, j, j + 1);
flag = true;
}
}
}
for (int i = 0; i < len;i++){
cout << a[i];
}
return 0;
}
该排序最大的特点就是交换移动数据次数少,这样也就节约了相应的时间。总时间复杂度与冒泡排序法一样依然为O(n2),但简单选择排序的性能上还是要略优于冒泡排序。
相当于建立一个变量记录最小值的索引,然后在j循环遍历结束后,判断如果i != min,就说明i不是最小值的索引,交换。
#include
using namespace std;
void swap(int* a,int i,int j){
int temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
int main(){
int a[] = {9,1,5,8,3,7,4,6,2};
int len=sizeof(a)/sizeof(a[0]);
for (int i = 0; i < len-1;i++){
int min = i;
for (int j = i+1; j <len;j++){
if(a[j]<a[min]){
min = j;
}
}
swap(a, i, min);
}
for (int i = 0; i < len;i++){
cout << a[i];
}
return 0;
}
对InsertSort函数做一个解释,首先第一层for循环从1开始,也就是假设a[0]已经放好了位置,后面的元素无非是决定插入它的左侧还是右侧的问题。然后开始a[i-1](已经放好位置的最后一个元素)>a[i]进行比较,其实也就是判断已经放好位置的最后一个元素和还没放好位置的的第一个元素的大小。
如果a[i]更大,说明a[i]应该放在a[i-1]的右侧,那正好,不用动了。
如果a[i-1]更大,说明a[i]应该放在a[i-1]的左侧,这时用temp记录a[i]的值,通过第二个for循环将之前已经排好序的数组元素依次右移一个位置,直到索引j=-1(也就意味着temp比当前所有已排好序的数组元素都小,所以放在了最左边)或者发现了a[j]<=temp,那么此时temp就可以放在a[j+1]的位置。
在整个代码中,a[j+1]始终代表了空的位置,因为在第一个j+1时的值已经被temp记录了下来,后面的都是依次右移。
#include
using namespace std;
void swap(int* a,int i,int j){
int temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
void InsertSort(int* a,int len){
int temp,j;
for (int i = 1; i < len;i++){
if(a[i-1]>a[i]){
//temp存储了a[i]的值,那么此时a[i]就相当于空了
//此时我们需要把i之前大于temp的值统统向后移动一个位置
temp = a[i];
for (j = i - 1; a[j] > temp&&j>=0;j--){
a[j + 1] = a[j];
}
a[j + 1] = temp;
}
}
}
int main(){
int a[] = {9,1,5,8,3,7,4,6,2};
// int a[] = {4,3,6,2,9,1,0};
int len=sizeof(a)/sizeof(a[0]);
InsertSort(a,len);
for (int i = 0; i < len;i++){
cout << a[i];
}
return 0;
}
直接插入排序算法的时间复杂度为O(n2),但它的性能比前两种又要好一点。
#include
using namespace std;
void swap(int a[],int begin,int end){
int temp = a[begin];
a[begin] = a[end];
a[end] = temp;
}
//传入的s为要调整的节点,使得以s为根节点的树变成一个大顶堆
void heapadjust(int a[],int s,int m){
int temp = a[s];
int j;
for (j = 2 * s; j <= m;j*=2){
//2*s为s的左节点,2*s+1为s的右节点,这里是找到以s为双亲结点的左右节点的最大值,其在数组中的下表为j
if(j+1<=m&&a[j]<a[j+1]){
j++;
}
//开始判断左右节点的最大值是否大于其双亲结点,如果不大于的话,那么就直接跳出循环
//为什么可以直接跳出去呢?即使该节点的左右节点没有大于该节点的,但该节点的左右节点下面的节点有可能有大于的啊
//因为heapsort()在第一个for循环中最先调用heapadjust()去调整的是a[0] / 2节点,堆具有完全二叉树的所有性质,所以
//a[0] / 2节点其实就是最后一个非叶节点,对照上面的图就是30,然后再从右往左、从下往上,对每一个非叶节点调用
//heapadjust()函数进行堆排序,使得其该节点的值大于左右两个子节点。因此在heapsort()第一个for循环后,数组第
//一个值最大。将第一个(也就是确定好的最大值)放在最后面(也就是与最后面的元素交换)
if(temp>=a[j]){
break;
}
a[s] = a[j];
s = j;//只要还在for循环内,就说明寻找的节点s的子节点j比s更大,此时我们要调整的就是j,看它的子节点是否有大于它的值。
}
a[s] = temp;
}
void heapsort(int a[]){
for (int i = a[0] / 2; i >= 1;i--){
heapadjust(a, i, a[0]);
}
//为什么j>1?因为下面每次循环都是把第一个(也是最大值)与最后的值交换,再调整使第一个为最大值
for (int j = a[0]; j > 1;j--){
swap(a, 1, j);
heapadjust(a, 1, j - 1);
}
}
int main(){
int a[] = {9,50, 10, 90, 30, 70, 40,80,60,20};
heapsort(a);
for (int i = 1; i <= a[0];i++){
cout << a[i]<<endl;
}
}
堆排序的时间复杂度为O(nlogn)。不适合待排序序列个数比较少的情况
template<typename keyname>
void swap(keyname &a,int begin,int end){
int temp = a[begin];
a[begin] = a[end];
a[end] = temp;
}
/* 快速排序优化算法 */
template<typename keyname>
int Partition1(keyname &a,int low,int high)
{
int pivotkey;
pivotkey=a[low]; /* 用子表的第一个记录作枢轴记录 */
int temp=pivotkey; /* 将枢轴关键字备份到temp */
while(low<high) /* 从表的两端交替地向中间扫描 */
{
while(low<high&&a[high]>=pivotkey)
high--;
a[low]=a[high];
while(low<high&&a[low]<=pivotkey)
low++;
a[high]=a[low];
}
a[low]=temp;
return low; /* 返回枢轴所在位置 */
}
template<typename keyname>
void QSort1(keyname &a,int low,int high)
{
int pivot;
pivot=Partition1(a,low,high); /* 将a[low..high]一分为二,算出枢轴值pivot */
if(low<pivot-1)//加if判断是为了避免陷入死循环
QSort1(a,low,pivot-1);
if(pivot-1<high) /* 对低子表递归排序 */
QSort1(a,pivot+1,high); /* 对高子表递归排序 */
}
int main(){
vector<int>a={50,10,90,30,70,80,60,20};
int len = a.size();
QSort1(a, 0,len-1);
for (int i = 0; i < len;i++){
cout << a[i] << endl;
}
}