常用的数据结构和算法总结

选择排序 Selection Sort

算法思想

  1. 第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置

  2. 然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾

  3. 直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

     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)是对冒泡排序的一种改进。
快速排序算法通过多次比较和交换来实现排序,其排序流程如下:

  1. 首先设定一个分界值,通过该分界值将数组分成左右两部分。
  2. 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分2020/2/9 21:52:49 2020/2/9 21:52:51 中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
  3. 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
  4. 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。


 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

算法思想

可是由于数组中元素位置的改变,我们将面临着几个局限性。

  1. 如果我们的元素是十分复杂的话,比如像每个位置上存的是一篇10万字的文章。那么交换它们之间的位置将产生大量的时间消耗。(不过这可以通过技术手段解决
  2. 由于我们的数组元素的位置在构建成堆之后发生了改变,那么我们之后就很难索引到它,很难去改变它。例如我们在构建成堆后,想去改变一个原来元素的优先级(值),将会变得非常困难。
  3. 可能我们在每一个元素上再加上一个属性来表示原来位置可以解决,但是这样的话,我们必须将这个数组遍历一下才能解决。(性能低效)

针对以上问题,我们就需要引入索引堆(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

###二分搜索树的优势

  1. 高效 不仅可以查找数据,还可以高效地插入,删除数据-动态维护数据
  2. 可以方便地回家很多数据之间的关系问题 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值

二分搜索树的局限性

  • 同样的数据,可以对应不同的二分搜索树
  • 二分搜索树可能退化成链表
  • 防止退化成链表—平衡二叉树:红黑树
原创文章 4 获赞 3 访问量 124

猜你喜欢

转载自blog.csdn.net/qq_34795226/article/details/105385879