閱讀472 返回首頁    go 阿裏雲 go 技術社區[雲棲]


排序算法係列之二叉查找樹

排序算法係列之二叉查找樹

基本概念

  二叉查找樹(Binary Search Tree),或者是一棵空樹,或者是具有下列性質的二叉樹:

  1. 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
  2. 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
  3. 它的左、右子樹也分別為二叉排序樹。
                                  
         序列:1 3 4 6 7 8 10 13 14                 序列:2 3 4 6 7 9 13 15 17 18 20               
   通常采取二叉鏈表作為二叉排序樹的存儲結構。中序遍曆二叉排序樹可得到一個關鍵字的有序序列,一個無序序列可以通過構造一棵二叉排序樹變成一個有序序列,構造樹的過程即為對無序序列進行排序的過程。每次插入的新的結點都是二叉排序樹上新的葉子結點,在進行插入操作時,不必移動其它結點,隻需改動某個結點的指針,由空變為非空即可。

二叉查找樹查找算法思想

在二叉查找樹b中查找x的過程為:

  1. 若b是空樹,則搜索失敗,否則:
  2. 若x等於b的根節點的數據域之值,則查找成功;否則:
  3. 若x小於b的根節點的數據域之值,則搜索左子樹;否則:
  4. 查找右子樹。
舉例圖:

二叉查找樹插入算法思想

向一個二叉查找樹b中插入一個節點s的算法,過程為:

  1. 若b是空樹,則將s所指結點作為根節點插入,否則:
  2. 若s->data等於b的根節點的數據域之值,則返回,否則:
  3. 若s->data小於b的根節點的數據域之值,則把s所指節點插入到左子樹中,否則:
  4. 把s所指節點插入到右子樹中。
舉例圖:
                            

二叉查找樹刪除算法思想

在二叉排序樹刪去一個結點,分三種情況討論:

  1. 若*p結點為葉子結點,即PL(左子樹)和PR(右子樹)均為空樹。由於刪去葉子結點不破壞整棵樹的結構,則隻需修改其雙親結點的指針即可。
  2. 若*p結點隻有左子樹PL或右子樹PR,此時隻要令PL或PR直接成為其雙親結點*f的左子樹(當*p是左子樹)或右子樹(當*p是右子樹)即可,作此修改也不破壞二叉排序樹的特性。
  3. 若*p結點的左子樹和右子樹均不空。在刪去*p之後,為保持其它元素之間的相對位置不變,可按中序遍曆保持有序進行調整,可以有兩種做法:其一是令*p的左子樹為*f的左/右(依*p是*f的左子樹還是右子樹而定)子樹,*s為*p左子樹的最右下的結點,而*p的右子樹為*s的右子樹;其二是令*p的直接前驅(或直接後繼)替代*p,然後再從二叉排序樹中刪去它的直接前驅(或直接後繼)。
舉例圖:刪除節點值 6。方便記憶:節點6的右孩子直接替換刪除節點,節點6的左孩子掛在節點6的右孩子上。

二叉查找樹性能分析

    每個結點的C_i為該結點的層次數。最壞情況下,當先後插入的關鍵字有序時,構成的二叉排序樹蛻變為單支樹,樹的深度為n,其平均查找長度為\frac{n+1}{2}(和順序查找相同),最好的情況是二叉排序樹的形態和折半查找的判定樹相同,其平均查找長度和\log_2(n)成正比。

二叉查找樹C語言實現

代碼來自https://chiyx.iteye.com/blog/1628947
c語言.h代碼
/*file:biTree.h*/
#ifndef CHIYX_BITREE
#define CHIYX_BITREE
#ifndef NULL
#define NULL 0
#endif
typedef int DataType;
//二叉樹的節點結構
typedef struct BiTreeNode {
	DataType data;
	struct BiTreeNode *parent;
	struct BiTreeNode *left;
	struct BiTreeNode *right;
}BiTreeNode, *BiTree;

//查找:返回第一個等於data域等於key的節點,不存在返回NULL
BiTreeNode *search(BiTree *biTree, DataType key);
//返回二叉樹的最小節點,空樹返回NULL
BiTreeNode *minImum(BiTree *biTree);
//返回二叉樹的最大節點,空樹返回NULL
BiTreeNode *maxImum(BiTree *biTree);
//返回節點x的後繼節點,不存在後繼(節點x為最大節點)返回NULL
BiTreeNode *successor(BiTreeNode *x);
//返回節點x的前驅節點,不存在前驅(節點x為最小節點)返回NULL
BiTreeNode *predecessor(BiTreeNode *x);
//將值data插入到二叉樹中(生成一個值為data的節點)
void insertNode(BiTree *biTree, DataType data);
//刪除一個值為data的節點
void deleteNode(BiTree *biTree, DataType data);
//中序遍曆二叉樹
void inorderTraversal(BiTree *biTree, void (*visitor)(BiTreeNode *node));
#endif
C語言實現代碼:
/*file:biTree.c*/
#include <stdlib.h>
#include "biTree.h"

//查找:返回第一個等於data域等於key的節點,不存在返回NULL
BiTreeNode *search(BiTree *biTree, DataType key) {
	BiTreeNode *curNode = *biTree;
	while (curNode != NULL && curNode->data != key) {
		if (key < curNode->data) {
			curNode = curNode->left;
		} else {
			curNode = curNode->right;
		}
	}
	return curNode;
}
//返回二叉樹的最小節點,空樹返回NULL
BiTreeNode *minImum(BiTree *biTree) {
	BiTreeNode *curNode = *biTree;
	while (curNode != NULL && curNode->left != NULL) {
		curNode = curNode->left;
	}
	return curNode;
}
//返回二叉樹的最大節點,空樹返回NULL
BiTreeNode *maxImum(BiTree *biTree) {
	BiTreeNode *curNode = *biTree;
	while (curNode != NULL && curNode->right != NULL) {
		curNode = curNode->right;
	}
	return curNode;
}

//返回節點x的後繼節點,不存在後繼(節點x為最大節點)返回NULL
BiTreeNode *successor(BiTreeNode *x) {
         if (x == NULL) return NULL;
	//存在右子樹,則後繼節點為其右子樹中最小的節點
	if (x != NULL && x->right != NULL) {
		return minImum(&(x->right));
	}
	while (x->parent != NULL && x->parent->right == x) {
		x = x->parent;
	}
	return x->parent; //錯誤版本為 x, 此處應該返回父結點
}
//返回節點x的前驅節點,不存在前驅(節點x為最小節點)返回NULL
BiTreeNode *predecessor(BiTreeNode *x) {
         if (x == NULL) return NULL;
	//存在左子樹,則後繼節點為其左子樹中最大的節點
	if (x != NULL && x->left != NULL) {
		return maxImum(&(x->left));
	}
	while (x->parent != NULL && x->parent->left == x) {
		x = x->parent;
	}
	return x->parent; //錯誤版本為 x, 此處應該返回父結點

}

void insertNode(BiTree *biTree, DataType data) {
	//創建節點
	BiTreeNode *targetNode;

	targetNode = (BiTreeNode *)malloc(sizeof(BiTreeNode));
	//沒有足夠內存
	if (targetNode == NULL) return;
	targetNode->data = data;
	targetNode->parent = NULL;
	targetNode->left = NULL;
	targetNode->right = NULL;

	BiTreeNode *p, *y; 
	p = *biTree;
	y = NULL;
	while (p != NULL ) {
		y = p;
		if (targetNode->data < p->data) {
			p = p->left;
		} else {
			p = p->right;
		}
	}
	//空樹,將新節點置為樹根
	if (y == NULL) {
		*biTree = targetNode;
	} else {
		if (targetNode->data < y->data) {
			y->left = targetNode;
		} else {
			y->right = targetNode;
		}
	}
	targetNode->parent = y;
}
//刪除一個值為data的節點
void deleteNode(BiTree *biTree, DataType data) {
	//查找待刪除的節點
	BiTreeNode *targetNode, *x, *y;

	targetNode = search(biTree, data);
	if (targetNode == NULL) return;
	//找出真正的刪除節點,如果目標節點最多隻有一個子樹,則其為真正刪除的節點
	//否則其後繼節點(最多隻有一個子樹,想想為什麼)為真正刪除的節點,然後將後繼節點的值賦給目標節點
	if (targetNode->left == NULL || targetNode->right == NULL) {
		y = targetNode;
	} else {
		y = successor(targetNode);
	}

	if (y->left != NULL) {
		x = y->left;
	} else {
		x = y->right;
	}

	if (x != NULL) {
		x->parent = y->parent;
	}

	//如果y是根節點, 則根節點變為x
	if (y->parent == NULL) {
		*biTree = x;
	} else {
		if (y->parent->right == y) {
			y->parent->right = x;
		} else {
			y->parent->left = x;
		}
	}

	if (y != targetNode) {
		targetNode->data = y->data;
	} 
	//釋放y占有的空間
	free(y);
}
//中序遍曆二叉樹
void inorderTraversal(BiTree *biTree, void (*visitor)(BiTreeNode *node)) {
	BiTreeNode *curNode;

	curNode = *biTree;
	if (curNode != NULL) {
		//遍曆左子樹
		inorderTraversal(&(curNode->left), visitor);
		//訪問節點
		visitor(curNode);
		//遍曆右子樹
		inorderTraversal(&(curNode->right), visitor);
	}
}

#include <stdio.h>
#include <stdlib.h>
#include "biTree.h"
#define N  10
void printNode(BiTreeNode *node);

int main(int argc, char *argv[]) {
	BiTreeNode *root;
	int i;

	root = NULL;
	int data[N] = {10, 23, 11, 98, 111, 87, 34, 11, 33, 8};
	for (i = 0; i < N; i++) {
		insertNode(&root, data[i]);
	}
	printf("before delete:\n");
	inorderTraversal(&root, printNode);
	printf("\n");
	deleteNode(&root, 11);
	deleteNode(&root, 8);
	printf("after delete:\n");
	inorderTraversal(&root, printNode);
	printf("\n");
	exit(0);
}
void printNode(BiTreeNode *node) {
	printf("%d\t", node->data);
}

二叉查找樹優化

  雖然二叉排序樹的最壞效率是O(n),但它支持動態查詢,且有很多改進版的二叉排序樹可以使樹高為O(logn):
  1. Size Balanced Tree(SBT)
  2. AVL樹
  3. 紅黑樹(紅黑樹係列文章)
  4. Treap(Tree+Heap)

這些均可以使查找樹的高度為O(\log(n))

後續再更!




最後更新:2017-04-03 19:13:18

  上一篇:go 輕量級的HTTP開發庫 Unirest
  下一篇:go TCP詳解(三次握手/四次揮手詳解)