目次
オレンジ色
探す
バイナリソートツリー
コード例
#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;
}
要約する
つまり、バイナリ ソート ツリーはリンクされた形式で格納されるため、挿入または削除操作を実行する際に要素を移動する必要がないというリンク ストレージ構造の利点が維持されます。リンク ポインタのみを変更する必要があります。挿入と削除の時間パフォーマンスは比較的良好です。2 値ソート ツリーでの検索の場合、ルート ノードから検索対象のノードまでのパスが取られ、比較の回数は 2 値ソート ツリー内の特定の値を持つノードのレベルの数に等しくなります。極端な場合には、それは少なくとも 1 回あります。つまり、ルート ノードが探しているノードであり、最大でもツリーの深さを超えることはありません。つまり、バイナリ ソート ツリーの検索パフォーマンスは、バイナリ ソート ツリーの形状に依存します。しかし問題は、バイナリソートツリーの形状が不確実であることです。
たとえば、{62,88,58,47,35,73,51,99,37,93} のような配列の場合、次の図に示すようなバイナリ ソート ツリーを構築できます。ただし、配列要素の順序が小さいものから大きいものへ ({35,37,47,51,58,62,73,88,93,99} など) の場合、バイナリ ソート ツリーは極端に右に歪んだツリーになります。注: 以下の右側に示すように、これはまだバイナリ ツリーです。このとき、ノード 99 も探しています。左下の図では 2 回の比較のみが必要ですが、右下の図では結果を得るまでに 10 回の比較が必要であり、この 2 つには大きな違いがあります。
言い換えれば、バイナリ ソート ツリーが比較的バランスが取れていること、つまり、その深さが完全なバイナリ ツリーの深さと同じ (logn) + 1 であることを望みます。その場合、検索時間の計算量は O(logn) になります。実際、上図の左上の画像もバランスが悪く、明らかに左側が重く、右側が軽いです。
不均衡の最悪のケースは、右上のような歪んだツリーであり、検索時間の計算量は O(n) であり、順次検索と同等です。
したがって、バイナリ ソート ツリーに従ってセットを検索したい場合は、バランスのとれたバイナリ ソート ツリーとして構築するのが最善です。これは、バイナリ ソート ツリーのバランスをどのようにとるかという別の質問につながります。
バランス二分木 (AVL ツリー)
コード
かなりわかりにくいコードですが、こちらのブログ「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;
}
ハッシュ表
コード
このコードには、初期化、挿入、検索という 3 つの関数があります。このコードでは、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<iostream>
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 に初期化されます。あるソートの際に入れ替えが起こらない、つまりフラグが常に false であれば、配列はすでに整っていることを意味し、無意味なループ判定を行う必要はありません。
#include<iostream>
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(n 2 )ですが、単純な選択ソートのパフォーマンスは依然としてバブル ソートよりわずかに優れています。
これは、変数レコードの最小値のインデックスを確立することに相当し、j ループのトラバーサルの終了後、i != min の場合、i は最小値のインデックスではないことを意味すると判断されます。交換。
#include<iostream>
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段目は1から始まります。つまり、a[0]が置かれていたとして、以降の要素はそれを上に挿入するかどうかを決めるだけです。左側または右側。次に、a[i-1] (最後に配置された要素) > a[i] の比較を開始します。実際には、最後に配置された要素とまだ配置されていない最初の要素を判断します。のサイズ。
a[i] の方が大きい場合は、a[i-1] の右側に a[i] を配置する必要があることを意味します。これでちょうどよく、移動する必要はありません。
a[i-1] の方が大きい場合は、a[i-1] の左側に a[i] を配置することを意味します。このとき、temp を使用して a[i] の値を記録し、 2 番目の for ループは前にソートします。ソートされた配列要素は、インデックス j=-1 になるまで順番に 1 つずつ右にシフトされます (つまり、temp は現在ソートされているすべての配列要素より小さいため、左端に配置されます)。または、a[j] <=temp が見つかった場合は、temp を a[j+1] の位置に配置できます。
最初の j+1 の値は temp によって記録されており、後続の値は順番に右にシフトされるため、コード全体を通じて、a[j+1] は常に空の位置を表します。
#include<iostream>
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(n 2 ) ですが、そのパフォーマンスは最初の 2 つよりわずかに優れています。
ヒープソート
a = {5, 43, 14, 66, 34, 25},
#include<iostream>
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;
}
}
時間計算量は O(nlogn) です。平均して、空間の複雑さも O(logn) です。