选择排序 Selection Sort
算法思想
-
第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置
-
然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾
-
直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
template<typename T> void selectionSort(T arr[], int n) { for (int i = 0; i < n; i++) { //寻找(i,n)区间里的最小值 int minIndex = i; for (int j = i + 1; j < n; j++) { if (arr[j] < arr[minIndex]) minIndex = j; } swap(arr[i], arr[minIndex]); } }
缺点:选择排序是一种不稳定的排序算法。
改进后的选择排序算法
可以在每一轮循环中同时找到当前数据的最大值和最小值
template<typename T>
void selectionSort2(T arr[], int n) {
int left = 0, right = n - 1;
while (left < right) {
int minIndex = left;
int maxIndex = right;
// 在每一轮查找时, 要保证arr[minIndex] <= arr[maxIndex]
if (arr[minIndex] > arr[maxIndex])
swap(arr[minIndex], arr[maxIndex]);
for (int i = left + 1; i < right; i++)
if (arr[i] < arr[minIndex])
minIndex = i;
else if (arr[i] > arr[maxIndex])
maxIndex = i;
swap(arr[left], arr[minIndex]);
swap(arr[right], arr[maxIndex]);
left++;
right--;
}
return;
}
插入排序 Insertion Sort
算法思想
每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的记录中适当位置上,直到全部插入完为止。
插入排序算法
template<typename T>
void insertionSort(T arr[], int n) {
for (int i = 1; i < n; i++) {
//寻找元素arr[i]合适的插入位置//寻找元素arr[i]合适的插入位置
for (int j = i; j > 0 && arr[j-1]>arr[j]; j--) {
swap(arr[j],arr[j-1]);
}
}
}
改进后的插入排序算法
改进后的相对于之前的减少了赋值次数,提高了算法的效率
template<typename T>
void insertionSort(T arr[], int n) {
for (int i = 1; i < n; i++) {
//寻找元素arr[i]合适的插入位置//寻找元素arr[i]合适的插入位置
T e = arr[i];
int j;
for (j = i; j > 0 && arr[j - 1]>e; j--) {
arr[j] = arr[j - 1];
}
arr[j] = e;
}
}
优点:插入排序算法对于有效数组的排序效率高,甚至比时间复杂度为O(NlogN)的排序算法的性能更好。是一种稳定的排序算法。
冒泡排序 Bubble Sort
算法思想
两两比较相邻记录的值,如果反序则交换,直到没有反序的记录为止
template<typename T>
void bubbleSort(T arr[], int n) {
bool swapped;
do {
swapped = false;
for (int i = 1; i < n; i++)
if (arr[i - 1] > arr[i]) {
swap(arr[i - 1], arr[i]);
swapped = true;
}
// 优化, 每一趟Bubble Sort都将最大的元素放在了最后的位置
// 所以下一次排序, 最后的元素可以不再考虑
n--;
} while (swapped);
}
改进后的冒泡排序算法
template<typename T>
void bubbleSort2(T arr[], int n) {
int exchange;
do {
exchange = 0;
for (int i = 1; i < n; i++)
if (arr[i - 1] > arr[i]) {
swap(arr[i - 1], arr[i]);
exchange = i;
}
n = exchange;
} while (exchange > 0);
}
改进后的冒泡排序算法,会在每一次冒泡结束后记录下最后一次数据交换的位置。
冒泡排序是一种稳定的排序算法。
希尔排序 Shell Sort
算法思想
希尔排序是对直接插入排序的一种改进,改进的着眼点是:
- 若待排序记录按关键码基本有序,直接插入排序的效率很高
- 由于直接排序算法很简单,则在待排序记录个数较少时效率也很高
基本思想是:希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
template<typename T>
void shellSort(T arr[], int n) {
for (int d = n / 2; d >= 1; d = d / 2) {
for (int i = d + 1; i <= n; i++) {
T e = arr[i];
int j;
for (j = i - d; j > 0 && e < arr[j]; j = j - d) {
arr[j + d] = arr[j];
}
arr[j + d] = e;
}
}
}
希尔排序的另一种实现形式
template<typename T>
void shellSort2(T arr[], int n) {
// 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...
int h = 1;
while (h < n / 3)
h = 3 * h + 1;
while (h >= 1) {
// h-sort the array
for (int i = h; i < n; i++) {
// 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序
T e = arr[i];
int j;
for (j = i; j >= h && e < arr[j - h]; j -= h)
arr[j] = arr[j - h];
arr[j] = e;
}
h /= 3;
}
}
归并排序 Merge Sort#
###算法思想
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。
template<typename T>
void __merge(T arr[], int l,int mid, int r) {
T *aux = new T[r-l+1];
for (int i = l; i <=r ; i++) {
aux[i - l] = arr[i];
}
int i= l;
int j = mid + 1;
int k = l;
while (i < mid && j < r) {
arr[k++] = aux[i-l] < aux[j-l] ? aux[i-l] : aux[j-l];
i++;
j++;
}
while (i < j) {
arr[k++] = aux[i-l];
i++;
}
while (j < r) {
arr[k++] = aux[j-l];
j++;
}
delete[] aux;
}
//递归使用归并排序,对arr[l...r]的范围进行排序
template<typename T>
void __mergeSort(T arr[], int l, int r) {
if ( l>=r) {
return;
}
int mid = (l + r) / 2;
__mergeSort(arr,l,mid);
__mergeSort(arr,mid+1,r);
__merge(arr, l,mid, r);
}
template<typename T>
void MergeSort(T arr[], int n) {
__mergeSort(arr, 0, n - 1);
}
对于随机数据,归并排序显然比插入排序等O(n2)的排序方法效率好很多。但对于有序数据,插入排序近乎可以达到O(n)的效率,比归并排序快很多,为了解决这个问题,可以对归并排序进行优化。
###归并排序的优化
template<typename T>
void __mergeSort(T arr[], int l, int r) {
/*if (l >= r) {
return;
}*/
//数据少的时候使用插入排序效率更高
if (r - l < 15) {
insertionSort(arr, l, r);
return;
}
int mid = (l + r) / 2;
__mergeSort(arr, l, mid);
__mergeSort(arr, mid + 1, r);
//对两个归并完的数组的最大值和最小值进行比较
if(arr[mid] > arr[mid +1])
__merge(arr, l, mid, r);
}
###自底向上的归并排序
// Merge Sort BU 也是一个O(nlogn)复杂度的算法,虽然只使用两重for循环
// 所以,Merge Sort BU也可以在1秒之内轻松处理100万数量级的数据
// 注意:不要轻易根据循环层数来判断算法的复杂度,Merge Sort BU就是一个反例
template<typename T>
void MergeSortBU(T arr[], int n) {
for (int sz = 1; sz < n; sz += sz) {
for (int i = 0; i+sz< n; i += sz + sz) {
__merge(arr,i,i+sz-1,min((i+sz+sz-1),n-1));
}
}
}
// 比较Merge Sort和Merge Sort Bottom Up两种排序算法的性能效率
// 整体而言, 两种算法的效率是差不多的。
###自底向上的归并排序的优化
for( int i = 0 ; i < n ; i += 16 )
insertionSort(arr,i,min(i+15,n-1));
for( int sz = 16; sz < n ; sz += sz )
for( int i = 0 ; i < n - sz ; i += sz+sz )
// 对于arr[mid] <= arr[mid+1]的情况,不进行merge
if( arr[i+sz-1] > arr[i+sz] )
__merge(arr, i, i+sz-1, min(i+sz+sz-1,n-1) );
快速排序 Quick Sort
算法思想
快速排序(Quicksort)是对冒泡排序的一种改进。
快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
- 首先设定一个分界值,通过该分界值将数组分成左右两部分。
- 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分2020/2/9 21:52:49 2020/2/9 21:52:51 中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
- 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
- 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
template<typename T>
int __partition(T arr[], int l, int r) {
T e = arr[l];
int j = l;
for (int i = l + 1; i <= r; i++) {
if (arr[i] < e) {
j++;
swap(arr[j],arr[i]);
}
}
swap(arr[l],arr[j]);
return j;
}
template<typename T>
void __quickSort(T arr[], int l, int r) {
if (l >= r) {
return;
}
int p = __partition(arr, l, r);
__quickSort(arr,l,p-1);
__quickSort(arr, p + 1, r);
}
// 比较Merge Sort和Quick Sort两种排序算法的性能效率
// 两种排序算法虽然都是O(nlogn)级别的, 但是Quick Sort算法有常数级的优势
// Quick Sort要比Merge Sort快, 即使我们对Merge Sort进行了优化
template<typename T>
void QuickSort(T arr[], int n) {
__quickSort(arr,0,n-1);
}
###快速排序的优化
随机化快速排序法,对于基本有序的数组,可以让快速排序提高性能。
template<typename T>
int __partition(T arr[], int l, int r) {
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr[l] , arr[rand()%(r-l+1)+l] );
T e = arr[l];
int j = l;
for (int i = l + 1; i <= r; i++) {
if (arr[i] < e) {
j++;
swap(arr[j],arr[i]);
}
}
swap(arr[l],arr[j]);
return j;
}
###双路快速排序法
对于有大量重复的数据的排序效率更高
template <typename T>
int __partition2(T arr[], int l, int r) {
swap(arr[l], arr[rand() % (r - l + 1) + l]);
T v = arr[l];
int i = l + 1, j = r;
while (true) {
while (i <= r && arr[i] < v) i++;
while (j >= l + 1 && arr[j] > v)j--;
if (i > j) {
break;
}
swap(arr[i], arr[j]);
i++;
j--;
}
swap(arr[l], arr[j]);
return j;
}
// 对arr[l...r]部分进行快速排序
template <typename T>
void __quickSort2(T arr[], int l, int r) {
/*if (l >= r)
return;*/
if (r - l < 15) {
insertionSort(arr, l, r);
return;
}
int p = __partition2(arr, l, r);
__quickSort2(arr, l, p - 1);
__quickSort2(arr, p + 1, r);
}
template <typename T>
void QuickSort2(T arr[], int n) {
srand(time(NULL));
__quickSort2(arr, 0, n - 1);
}
###三路快速排序法
快排是二路划分的算法。如果待排序列中重复元素过多,也会大大影响排序的性能。这时候,如果采用三路划分,则会很好的避免这个问题。
如果一个带排序列重复元素过多,我们先随机选取一个pivot,设为T,那么数列可以分为三部分:小于v,等于v,大于v:等于v的部分就无需再参与后续的递归调用了,速度自然就大大提升了效率是最高的。
template <typename T>
void __quickSort3Ways(T arr[], int l, int r) {
/*if (l >= r)
return;*/
if (r - l < 15) {
insertionSort(arr, l, r);
return;
}
//partition
swap(arr[l],arr[rand()%(r-l+1)+l]);
T v = arr[l];
int lt = l;
int gt = r + 1;
int i = l + 1;
while (i < gt) {
if (arr[i] > v ) {
swap(arr[i], arr[gt - 1]);
i++;
gt--;
}
else if(arr[i] < v){
swap(arr[i], arr[lt+1]);
i++;
lt++;
}
else{
i++;
}
}
swap(arr[l], arr[lt]);
__quickSort3Ways(arr,l,lt-1);
__quickSort3Ways(arr, gt, r);
}
template <typename T>
void QuickSort3Ways(T arr[], int n) {
srand(time(NULL));
__quickSort3Ways(arr, 0, n - 1);
}
###求逆序对
归并排序
###求第n大的数据
快速排序
堆排序 Heap Sort
算法思想
在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点)。堆中定义以下几种操作:
- 最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
- 创建最大堆(Build Max Heap):将堆中的所有数据重新排序
- 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算
首先要创建堆
template<typename Item>
class MaxHeap {
private:
Item* data;
int count;
int capacity;
void shiftUp(int k) {
while (data[k] > data[k / 2] && k > 1) {
swap(data[k], data[k / 2]);
k = k / 2;
}
}
void shiftDown(int k) {
Item e = data[k];//e 赋值
int tmp = 0;
//左孩子不存在时跳出
while (2 * k <= count) {
int j = 2 * k;
if (j + 1 <= count && data[j + 1] > data[j]) {
j++;
}
if (e >= data[j]) {
break;
}
data[k] = data[j];
k = j;
}
data[k] = e;
}
/*void shiftDown(int k) {
while (2*k<=count) {
int j = 2 * k;
if (j + 1 <= count && data[j + 1] > data[j])
j++;
if (data[k] >= data[j])
break;
swap(data[j], data[k]);
k = j;
}
}*/
public:
MaxHeap(int capacity) {
data = new Item[capacity + 1];
count = 0;
this->capacity = capacity;
}
MaxHeap(Item arr[], int n) {
data = new Item[n + 1];
capacity = n;
for (int i = 0; i < n; i++)
data[i+1] = arr[i];
count = n;
for (int i = count / 2; i > 0; i--)
shiftDown(i);
}
~MaxHeap() {
delete[] data;
}
int size() {
return count;
}
bool isEmpty() {
return count == 0;
}
void insert(Item item) {
assert(count + 1 <= capacity);
data[count + 1] = item;
count++;
shiftUp(count);
}
Item extractMax() {
assert(count > 0);
Item ret = data[1];
swap(data[1], data[count]);
count--;
shiftDown(1);
return ret;
}
// 获取最大堆中的堆顶元素
Item getMax() {
assert(count > 0);
return data[1];
}
};
第一种堆排序算法,先将将n个元素逐个插入到空堆里,时间复杂度为O(nlogn)
template<typename T>
void heapSort1(T arr[], int n) {
MaxHeap<T> maxheap = MaxHeap<T>(n);
for (int i = 0; i < n; i++)
maxheap.insert(arr[i]);
for (int i = n - 1; i >= 0; i--)
arr[i] = maxheap.extractMax();
}
第二种堆排序算法
Heapify创建堆的过程时间复杂度为O(n),相对来说对第一种速度要快
template<typename T>
void heapSort2(T arr[], int n) {
MaxHeap<T> maxheap = MaxHeap<T>(arr,n);
for (int i = n - 1; i >= 0; i--)
arr[i] = maxheap.extractMax();
}
###堆排序的优化
原地堆排序
原地堆排序算法不需要开辟额外的空间,也不需要对这些额外的空间进行操作,所以效率更高。
经过我的实验证明,如果原地队排序用shiftDown1,也就是没有优化过shiftDown,它的效率反而没有前两个好,如果用shiftDown2则会提高了客观的性能。
template<typename T>
void __shiftDown(T arr[], int k, int n) {
while (2 * k +1 < n) {
int j = 2 * k+1;
if (j + 1 < n && arr[j + 1] > arr[j])
j++;
if (arr[k] >= arr[j])
break;
swap(arr[j], arr[k]);
k = j;
}
}
template<typename T>
void __shiftDown2(T arr[], int k, int n) {
T e = arr[k];
while (2 * k + 1 <= n) {
int j = 2 * k;
if (j + 1 <= n && arr[j + 1] > arr[j])
j++;
if (arr[k] >= arr[j])
break;
arr[k] = arr[j];
k = j;
}
arr[k] = e;
}
template<typename T>
void heapSort(T arr[], int n) {
for (int i = (n - 2) / 2; i >= 0; i--) {
__shiftDown2(arr, i, n);
}
for (int i = n - 1; i > 0; i--) {
swap(arr[i], arr[0]);
__shiftDown2(arr,0,i);
}
}
索引堆 Index Heap
算法思想
可是由于数组中元素位置的改变,我们将面临着几个局限性。
- 如果我们的元素是十分复杂的话,比如像每个位置上存的是一篇10万字的文章。那么交换它们之间的位置将产生大量的时间消耗。(不过这可以通过技术手段解决
- 由于我们的数组元素的位置在构建成堆之后发生了改变,那么我们之后就很难索引到它,很难去改变它。例如我们在构建成堆后,想去改变一个原来元素的优先级(值),将会变得非常困难。
- 可能我们在每一个元素上再加上一个属性来表示原来位置可以解决,但是这样的话,我们必须将这个数组遍历一下才能解决。(性能低效)
针对以上问题,我们就需要引入索引堆(Index Heap)的概念。
对于索引堆来说,我们将数据和索引这两部分分开存储。真正表征堆的这个数组是由索引这个数组构建成的。(像下图中那样,每个结点的位置写的是索引号)
而在构建堆(以最大索引堆为例)的时候,比较的是data中的值(即原来数组中对应索引所存的值),构建成堆的却是index域
而构建完之后,data域并没有发生改变,位置改变的是index域。
可以添加一个辅助的反向数组,可以更快地找到index索引的值
template<typename Item>
class IndexMaxHeap {
private:
Item* data;
int count;
int *indexes;
int *reverse;
int capacity;
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
void shiftUp(int k) {
while (k > 1 && data[indexes[k / 2]] < data[indexes[k]]) {
swap(indexes[k / 2], indexes[k]);
reverse[indexes[k / 2]] = k / 2;
reverse[indexes[k]] = k;
k /= 2;
}
}
//void shiftDown(int k) {
// int temp = k;
// Item e = data[k];//e 赋值
// //左孩子不存在时跳出
// while (2 * k <= count) {
// int j = 2 * k;
// if (j + 1 <= count && data[indexes[j + 1]] > data[indexes[j]]) {
// j++;
// }
// if (e >= data[indexes[j]]) {
// break;
// }
// indexes[k] = indexes[j];
// k = j;
// }
// indexes[k] = temp;
//}
void shiftDown(int k) {
while (2 * k <= count) {
int j = 2 * k;
if (j + 1 <= count && data[indexes[j + 1]] > data[indexes[j]])
j += 1;
if (data[indexes[k]] >= data[indexes[j]])
break;
swap(indexes[k], indexes[j]);
reverse[indexes[k]] = k;
reverse[indexes[j]] = j;
k = j;
}
}
public:
IndexMaxHeap(int capacity) {
data = new Item[capacity + 1];
indexes = new Item[capacity+1];
reverse = new Item[capacity + 1];
for (int i = 0; i <= capacity; i++) {
reverse[i] = 0;
}
count = 0;
this->capacity = capacity;
}
~IndexMaxHeap() {
delete[] data;
delete[] indexes;
delete[] reverse;
}
int size() {
return count;
}
bool isEmpty() {
return count == 0;
}
void insert(int i,Item item) {
assert(count + 1 <= capacity);
assert(i+1>=1 && i+1<=capacity);
i++;
data[i] = item;
indexes[count + 1] = i;
reverse[i] = count + 1;
count++;
shiftUp(count);
}
Item extractMax() {
assert(count > 0);
Item ret = data[indexes[1]];
swap(indexes[1], indexes[count]);
reverse[indexes[1]] = 1;
reverse[indexes[count]] = 0;
count--;
shiftDown(1);
return ret;
}
int extractMaxIndex() {
assert(count > 0);
int ret = indexes[1] - 1;
swap(indexes[1], indexes[count]);
reverse[indexes[1]] = 1;
reverse[indexes[count]] = 0;
count--;
shiftDown(1);
return ret;
}
// 获取最大堆中的堆顶元素
Item getMax() {
assert(count > 0);
return data[1];
}
bool contain(int i) {
assert(i + 1 >= 1 && i + 1 <= capacity);
return reverse[i + 1] != 0;
}
Item getItem(int i) {
assert(contain(i));
return data[i+1];
}
void change(int i, Item newItem) {
assert(contain(i));
i++;
data[i] = newItem;
/*for (int j = 1; j <= count; j++) {
if (indexes[j] == i) {
shiftUp(j);
shiftDown(j);
return;
}
}*/
int j = reverse[i];
shiftUp(j);
shiftDown(j);
}
};
###二叉堆 斐波那契堆
###使用堆实现优先序列
在1000000个元素中选出前100名
快速排序的空间复杂度是O(logn),因为快速排序是采用递归的方式,需要logn的栈空间,同理归并排序也是,归并排序的空间复杂度为O(logn)+O(n),但相比于O(n),logn可以省略。
可以通过自定义比较函数,让排序算法不存在稳定性问题。
###二分查找法
//二分查找法,数组必须是有序的
//找到target,返回数组的索引
//找不到返回-1
template<typename T>
int binarySearch(T arr[],int n,T target) {
int l = 0, r = n - 1;
while (l<=r)
{
int mid = l + (r - l) / 2;//防止溢出
if (arr[mid] == target)
return mid;
else if (arr[mid] > target)
r = mid - 1;
else
l = mid + 1;
}
return -1;
}
template<typename T>
int __binarySearch2(T arr[], int l,int r, T target) {
if (l > r)
return -1;
int mid = l + (r-l) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] > target)
__binarySearch2(arr, l, mid - 1, target);
else
__binarySearch2(arr, mid + 1, r, target);
}
template<typename T>
int binarySearch2(T arr[], int n, T target) {
return __binarySearch2(arr, 0, n - 1, target);
}
int main() {
int n = 1000000;
int* a = new int[n];
for (int i = 0; i < n; i++)
a[i] = i;
// 测试非递归二分查找法
clock_t startTime = clock();
// 对于我们的待查找数组[0...N)
// 对[0...N)区间的数值使用二分查找,最终结果应该就是数字本身
// 对[N...2*N)区间的数值使用二分查找,因为这些数字不在arr中,结果为-1
for (int i = 0; i < 2 * n; i++) {
int v = binarySearch(a, n, i);
if (i < n)
assert(v == i);
else
assert(v == -1);
}
clock_t endTime = clock();
cout << "Binary Search (Without Recursion): " << double(endTime - startTime) / CLOCKS_PER_SEC << " s" << endl;
// 测试递归的二分查找法
startTime = clock();
// 对于我们的待查找数组[0...N)
// 对[0...N)区间的数值使用二分查找,最终结果应该就是数字本身
// 对[N...2*N)区间的数值使用二分查找,因为这些数字不在arr中,结果为-1
for (int i = 0; i < 2 * n; i++) {
int v = binarySearch2(a, n, i);
if (i < n)
assert(v == i);
else
assert(v == -1);
}
endTime = clock();
cout << "Binary Search (Recursion): " << double(endTime - startTime) / CLOCKS_PER_SEC << " s" << endl;
delete[] a;
system("pause");
return 0;
}
###floor ceil
###二分搜索树的优势
- 高效 不仅可以查找数据,还可以高效地插入,删除数据-动态维护数据
- 可以方便地回家很多数据之间的关系问题 min max ceil floor rank select
二叉搜索树不一定是完全二叉树
二叉搜索树的插入与查找,最大值,最小值,删除最大值最小值,删除任意节点
template<typename Key,typename Value>
class BST
{
private:
struct Node
{
Key key;
Value value;
Node *left;
Node *right;
Node(Key key,Value value) {
this->key = key;
this->value = value;
this->left = this->right = NULL;
}
Node(Node *node) {
this->key = node->key;
this->value = node->value;
this->left = node->left;
this->right = node->right;
}
};
Node *root;
int count;
public:
BST() {
root = NULL;
count = 0;
}
~BST() {
//TODO
destory(root);
}
int size() {
return count;
}
bool isEmpty() {
return count == 0;
}
void insert(Key key, Value value) {
root = insert(root, key, value);
}
bool contain(Key key) {
return contain(root, key);
}
//Value*可以返回NULL
Value* search(Key key) {
return search(root, key);
}
void preOrder() {
preOrder(root);
}
// 二分搜索树的中序遍历
void inOrder() {
inOrder(root);
}
// 二分搜索树的后序遍历
void postOrder() {
postOrder(root);
}
//二分搜索树的层序遍历
void levelOrder() {
queue<Node*> q;
q.push(root);
while (!q.empty()) {
Node *node = q.front();
q.pop();
cout << node->key << endl;
if (node->left) {
q.push(node->left);
}
if (node->right) {
q.push(node->right);
}
}
}
// 寻找二分搜索树的最小的键值
Key minimum() {
assert(count != 0);
Node* minNode = minimum(root);
return minNode->key;
}
// 寻找二分搜索树的最大的键值
Key maximum() {
assert(count != 0);
Node* maxNode = maximum(root);
return maxNode->key;
}
// 从二分搜索树中删除最小值所在节点
void removeMin() {
if (root)
root = removeMin(root);
}
// 从二分搜索树中删除最大值所在节点
void removeMax() {
if (root)
root = removeMax(root);
}
//从二分搜索树中删除任意节点
void remove(Key key) {
if (root)
root = remove(root,key);
}
private:
// 向以node为根的二分搜索树中, 插入节点(key, value), 使用递归算法
// 返回插入新节点后的二分搜索树的根
Node * insert(Node *node, Key key, Value value) {
if (node == NULL) {
count++;
return new Node(key, value);
}
if (key == node->key)
node->value = value;
else if (key < node->key)
node->left = insert(node->left, key, value);
else// key > node->key
node->right = insert(node->right, key, value);
return node;
}
bool contain(Node* node,Key key) {
if (node == NULL) {
return false;
}
if (node->key == key) {
return true;
}
else if (node->key > key) {
return contain(node->left, key);
}
else
{
return contain(node->right, key);
}
}
Value* search(Node* node, Key key){
if (node == NULL) {
return NULL;
}
if (node->key == key) {
return &(node->value);
}
else if (node->key > key) {
return search(node->left, key);
}
else
{
return search(node->right, key);
}
}
void preOrder(Node* node) {
if (node != NULL) {
cout << node->key << endl;
preOrder(node->left);
preOrder(node->right);
}
}
void inOrder(Node* node) {
if (node != NULL) {
inOrder(node->left);
cout << node->key << endl;
inOrder(node->right);
}
}
void postOrder(Node* node) {
if (node != NULL) {
postOrder(node->left);
postOrder(node->right);
cout << node->key << endl;
}
}
void destory(Node *node) {
if (node != NULL) {
destory(node->left);
destory(node->right);
delete node;
count--;
}
}
Node* minimum(Node *node) {
if (node->left == NULL) {
return node;
}
minimum(node->left);
}
Node* maximum(Node *node) {
if (node->right == NULL) {
return node;
}
maximum(node->right);
}
Node* removeMin(Node* node) {
if (node->left == NULL) {
Node* rightNode = node->right;
delete node;
count--;
return rightNode;
}
node->left = removeMin(node->left);
return node;
}
Node* removeMax(Node* node) {
if (node->right == NULL) {
Node* leftNode = node->left;
delete node;
count--;
return leftNode;
}
node->right = removeMax(node->right);
return node;
}
Node* remove(Node* node, Key key) {
if (node == NULL) {
return NULL;
}
if (key < node->key) {
node->left = remove(node->left, key);
return node;
}
else if(key > node->key){
node->right = remove(node->right, key);
return node;
}else{
if (node->left == NULL) {
Node* rightNode = node->right;
delete node;
count--;
return rightNode;
}
if (node->right == NULL) {
Node* leftNode = node->left;
delete node;
count--;
return leftNode;
}
Node *successor = new Node(minimum(node->right));
count++;
successor->left = node->left;
successor->right = removeMin(node->right);
delete node;
count--;
return successor;
}
}
};
二叉搜索树的floor与ceil
rank
支持重复元素的二分搜索树,为每个节点再赋一个count值
二分搜索树的局限性
- 同样的数据,可以对应不同的二分搜索树
- 二分搜索树可能退化成链表
- 防止退化成链表—平衡二叉树:红黑树