排序算法

一、选择排序

1、

#include <iostream>
#include <algorithm>
#include <windows.h>

using namespace std;

void selectionSort(int arry[], int n){

	for (int i = 0; i < n; i++){
		//寻找[i,n)区间的最小值
		int minIndex = i;
		for (int j = i+1; j < n; j++){
			//寻找[j,n]区间最小坐标的索引
			if (arry[minIndex]>arry[j]){
				minIndex = j;
			}
		}
		if (i != minIndex){
			swap(arry[i],arry[minIndex]);

		}

	}

}


int main(){
	int a[10] = {10,9,8,7,6,5,4,3,2,1};
	selectionSort(a, 10);
	for (int i = 0; i < 10; i++){
		cout << a[i] << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

 

 

2、使用模板(泛型)编写算法

#include <iostream>
#include <algorithm>
#include <windows.h>
#include <string>

using namespace std;

template<typename T>

void selectionSort(T arry[], int n){

	for (int i = 0; i < n; i++){
		//寻找[i,n)区间的最小值
		int minIndex = i;
		for (int j = i+1; j < n; j++){
			//寻找[j,n]区间最小坐标的索引
			if (arry[minIndex]>arry[j]){
				minIndex = j;
			}
		}
		if (i != minIndex){
			swap(arry[i],arry[minIndex]);

		}

	}

}


int main(){
	int a[10] = {10,9,8,7,6,5,4,3,2,1};
	selectionSort(a, 10);
	for (int i = 0; i < 10; i++){
		cout << a[i] << " ";
	}
	cout << endl;

	float b[10] = { 4.43, 9.8, 8.6, 5.7, 6.8, 9.5, 2.4, 1.3, 1.2, 6.1 };
	selectionSort(b, 10);
	for (int i = 0; i < 10; i++){
		cout << b[i] << " ";
	}
	cout << endl;

	string c[4] = {"D","C","A","B"};
	selectionSort(c, 4);
	for (int i = 0; i < 4; i++){
		cout << c[i] << " ";
	}
	cout << endl;


	system("pause");
	return 0;
}

 

二、插入排序

1、在排序的同时进行了好性能的交换操作,所以,虽然查询可能会提前终止,但是由于交换操作,性能还是比选择排序差

template<typename T>
void insertionSort(T arry[],int n){
	for (int i = 1; i < n; i++){
		//寻找元素arr[i]合适的插入位置
		for (int j = i; j >0; j--){
			if (arry[j] < arry[j-1]){
				swap(arry[j-1], arry[j]);
			}
			else
			{
				break;
			}
		}
	}
}

 

2、

一次交换是三次赋值,通过赋值操作取代交换操作

因为插入排序不仅不使用交换操作,而且可以提前终止。可以提前终止是插入操作很重要的性质,因为该性质,插入排序的效率比选择排序更高。

插入排序对于近乎有序的元素进行排序,有时候甚至比nlogn的算法排序更有效。

template<typename T>
void insertionSort(T arry[],int n){
	for (int i = 1; i < n; i++){
		T temp = arry[i];
		//寻找元素arr[i]合适的插入位置
		for (int j = i; j >0; j--){
			if (temp < arry[j-1]){
				arry[j] = arry[j-1];
			}
			else
			{
				arry[j] = temp;
				break;
			}
		}

	}
}

 

或者:

template<typename T>
void insertionSort(T arry[],int n){
	for (int i = 1; i < n; i++){
		T temp = arry[i];
		int j;
		//寻找元素arr[i]合适的插入位置
		for (j = i; j >0 && temp < arry[j - 1]; j--){
				arry[j] = arry[j-1];
			
		}
		arry[j] = temp;
	}
}

 

三、归并排序(MergeSort)

1、介绍

1.1 为什么要把数据分成一半,然后再逐渐归并呢?

如果有n个元素,那么到第log(N)层的时候,就分成一个元素了。也就是说层数是logn层级的。每一层要处理的个数是一样的,虽然把它分成了不同的部分。如果整个归并过程,可以以O(N)的过程解决的化,那么就设计出来Nlog(N)级别的算法。这也是Nlog(N)算法的主要来源,通常是通过二分法,得到logn这样的层级,之后,每一层级用O(N)级别的算法做事情

 

1.2 接下来的问题就是,将整个数组划分为两部分,这两部分分别排好序后,能否使用O(N)的算法将它们归并到一起,形成一个新的有序的数组呢?如果可以,那么就可以使用递归的过程,来完成整个归并排序。

1.3 怎么将排序好的数组,合并成一个有序数组呢?

通常需要开辟一个同样大小的临时的空间,来辅助我们完成这个归并的过程。

这也是归并排序的一个缺点,它可以将算法的复杂度提高到NlogN级别,但是,它比插入排序、选择排序多使用了存储空间。也就是说它需要使用O(N)额外的空间来完成这个排序。

 

2、自顶向下归并排序算法代码:

//将arry[l...mid]、arry[mid+1...r]两部分进行归并
template<typename T>
void __merge(T arry[], int l, int mid, int r){
	T aux[r - l + 1];
	//创建临时空间
	for (int i = l; i <= r; i++){
		aux[i - l] = arry[i];
	}

	int i = l, j = mid + 1;
	for (int k = l; k <= r; k++){
		if ( i > mid ){
			arry[k] = aux[j - l];
			j++;
		}
		else if (j > r){
			arry[k] = aux[i - l];
			i++;
		}
		else if (aux[i-l] < aux[j-l]){
			arry[k] = aux[i - l];
			i++;
		}
		else
		{
			arry[k] = aux[j - l];
			j++;
		}
	}

	
}

template<typename T>
//递归使用归并排序,对arry[l...r]范围的数组进行排序
void __mergeSort(T arry[], int l, int r){

	if (l >= r)
		return;

	int mid = (l+r)/2;
	__mergeSort(arry,l, mid);
	__mergeSort(arry, mid + 1, r);
	__merge(arry, l, mid,r);
}

template<typename T>
void mergeSort(T arry[], int n){
	__mergeSort(arry, 0, n - 1);
}

 

3、归并排序优化:

3.1 如果面对的是近乎有序的数组,那么只有再arr[mid]>arr[mid+1]时,归并的两部分合起来的整体才不是有序的,需要进行归并排序。那么需要加入判断:

if(arr[mid]>arr[mid+1])

3.2  对于所有的高级排序算法,都存在一种优化情况,那就是递归到底的情况。现在是递归到只有一个元素的时候返回回去,但是事实上,当递归到元素非常小的时候,可以转而使用插入排序来提高性能。这是基于两个原因,一方面是当元素数量非常小的时候,整个数组近乎有序的概率比较大,此时插入排序有优势;另一方面,虽然插入排序最差的情况是O(n*n)级别,归并排序是O(nlogn)级别,但是对于时间复杂度来说,这两者前面都是有一个常数系数的。对于这个系数而言,插入排序是比归并排序小的,换句话说,当n小到一定程度的时候,插入排序会比归并排序快一些。

因此,这段代码

可以修改为:

template<typename T>
void insertionSort(T arry[], int l,int r){
	for (int i = l+1; i <= r; i++){
		T temp = arry[i];
		int j;
		//寻找元素arr[i]合适的插入位置
		for (j = i; j >l && temp < arry[j - 1]; j--){
			arry[j] = arry[j - 1];

		}
		arry[j] = temp;
	}
}

//将arry[l...mid]、arry[mid+1...r]两部分进行归并
template<typename T>
void __merge(T arry[], int l, int mid, int r){
	T aux[r - l + 1];
	//创建临时空间
	for (int i = l; i <= r; i++){
		aux[i - l] = arry[i];
	}

	int i = l, j = mid + 1;
	for (int k = l; k <= r; k++){
		if ( i > mid ){
			arry[k] = aux[j - l];
			j++;
		}
		else if (j > r){
			arry[k] = aux[i - l];
			i++;
		}
		else if (aux[i-l] < aux[j-l]){
			arry[k] = aux[i - l];
			i++;
		}
		else
		{
			arry[k] = aux[j - l];
			j++;
		}
	}

	
}

template<typename T>
//递归使用归并排序,对arry[l...r]范围的数组进行排序
void __mergeSort(T arry[], int l, int r){

	//if (l >= r)
	//	return;

	if (r - l <= 15){
		insertionSort(arry, l, r);
		return;
	}

	int mid = (l+r)/2;
	__mergeSort(arry,l, mid);
	__mergeSort(arry, mid + 1, r);
	if (arry[mid] > arry[mid+1])
		__merge(arry, l, mid,r);
}

template<typename T>
void mergeSort(T arry[], int n){
	__mergeSort(arry, 0, n - 1);
}

4、自底向上归并排序代码:

4.1 过程

将数组每两个分为一组:

 

完成归并排序:

排序完一轮之后,再四个元素一组完成归并排序:

完成归并排序:

最后,8个小段一组完成归并排序:

在这个过程中,不需要递归,只需要迭代就可以完成归并排序。

在此过程中,需要处理两个边界问题:

首先,归并过程必须是两部分,如果是只有一部分,那么这部分已经是有序的,那么没有必要再排序。

其次,i+2*sz-1不能越界,所以要取i+2*sz-1与n-1的最小值

//将arry[l...mid]、arry[mid+1...r]两部分进行归并
template<typename T>
void __merge(T arry[], int l, int mid, int r){
	int* aux= new int[r-l+1];
	//T aux[r - l + 1];
	//创建临时空间
	for (int i = l; i <= r; i++){
		aux[i - l] = arry[i];
	}

	int i = l, j = mid + 1;
	for (int k = l; k <= r; k++){
		if ( i > mid ){
			arry[k] = aux[j - l];
			j++;
		}
		else if (j > r){
			arry[k] = aux[i - l];
			i++;
		}
		else if (aux[i-l] < aux[j-l]){
			arry[k] = aux[i - l];
			i++;
		}
		else
		{
			arry[k] = aux[j - l];
			j++;
		}
	}

	
}

template<typename T>
void mergeBUSort(T arry[], int n){
	for (int sz = 1; sz <= n; sz += sz){
		for (int i = 0; i + sz< n; i += sz + sz){
			//对arr[i..i+sz-1]和arr[i+sz...i+2*sz-1]进行归并
			__merge(arry, i, i+sz-1, min(i+2*sz-1,n-1));
	
		}
	}
}

 

四、快速排序

1、快速排序的基本思想:

每次从当前的数组中,考虑一个元素,以这个元素为基点,然后把这个点排到它在排好序后应该处在的位置。如下图4所处的位置。4这个元素处在这个位置,使得整个数组有一个性质,那就是4之前所有的元素都是小于4的,4之后所有的元素都是大于4的。之后要做的事情,就是对4之前的子数组和4之后的子数组,分别使用快速排序的思路进行快速排序,逐渐递归下去,完成快速排序过程。对于快速排序来说,最重要的就是如何把一个选定的元素,比如4,挪到正确的位置上。

 

2、排序过程

2.1 快速把选定的元素移动到正确的位置上,这个过程也是快速排序的核心。通常把这个子过程,叫做partition。也就是把整个数组分成两部分的过程。在这个过程中,通常使用整个数组的第一个元素来作为分界的标志点,叫做l。之后逐渐遍历右边的元素,在遍历过程中逐渐地整理,让整个数组,一部分是小于V的,另一部分是大于v的,将>V与<V的分界点记为j,当前访问的元素e记为i

 

2.2

如果当前访问的元素e比V大

那么直接归入>v这部分

然后,将i后移一位,i++

2.3 如果当前访问的元素e比V小,则需要想办法将e移动到<V的那部分,

 则需要将j+i位置的元素与e这个元素进行位置交换,

此时需要 j++

接着,i++遍历整个数组

 

2.4 通过这种方式,对整个数组进行遍历,遍历完成后,整个数组被分为了3部分。

第一个元素是V,橙黄色部分小于V,紫色部分大于V

最后需要做的就是对l和j两部分的元素进行交换,此时,整个数组被分为了<V、 =V 、 >V三部分了

 


//对arry[l...r]部分进行partition操作
//返回p,使得arr[l...p-1]<arr[p];  arr[p+1...r]>arr[p]
template<typename T>
int __partion(T arry[],int l,int r){
	T v = arry[l];

	//arry[l+1...j]<v;  arr[j+1...i)>v
	int j = l;
	for (int i = j + 1; i <= r; i++){
		if (arry[i]<v){
			swap(arry[j + 1], arry[i]);
			j++;
		}
	}
	swap(arry[l], arry[j]);
	return j;

}


template<typename T>
void __quickSort(T arry[], int l,int r){
	if (l >= r)
		return;

	int p = __partion(arry, l, r);
	__quickSort(arry, l, p-1);
	__quickSort(arry, p + 1, r);
}

template<typename T>
void quickSort(T arry[], int n){
	__quickSort(arry, 0, n - 1);
}

五、随机快速排序

所以,为了避免当数组有序的时候,所取的第一个值都是一个最小值或最大值,从而退化为O(n^2)的算法,需要随机取出一个数值,而不是第一个值

	swap(arr[l], arr[rand() % (r - l + 1) + l]);

 

代码为:

//对arry[l...r]部分进行partition操作
//返回p,使得arry[l...p-1]<arr[p];  arry[p+1...r]>arr[p]
template<typename T>
int __partion(T arry[],int l,int r){
	swap(arr[l], arr[rand() % (r - l + 1) + l]);
	T v = arry[l];

	//arry[l+1...j]<v;  arr[j+1...i)>v
	int j = l;
	for (int i = j + 1; i <= r; i++){
		if (arry[i]<v){
			swap(arry[j + 1], arry[i]);
			j++;
		}
	}
	swap(arry[l], arry[j]);
	return j;

}


template<typename T>
void __quickSort(T arry[], int l,int r){
	if (l >= r)
		return;

	int p = __partion(arry, l, r);
	__quickSort(arry, l, p-1);
	__quickSort(arry, p + 1, r);
}

template<typename T>
void quickSort(T arry[], int n){
	srand(time(NULL));
	__quickSort(arry, 0, n - 1);
}

 

六、双路快排

当数组中存在大量 =V 的数据时,都有可能将数组分为极度不平衡的两部分

从左向右,当访问到元素e<v时,i++

从右向左,当访问到元素e>v时,j--

 

从左向右e>=v或者从右向左e<=v时,将i和j的值进行交换

最大的不同是将=v的元素分步到了左、右两部分

其实,分为的是<=V和>=V两部分


//对arry[l...r]部分进行partition操作
//返回p,使得arr[l...p-1]<arr[p];  arr[p+1...r]>arr[p]
template<typename T>
int __partion2(T arry[], int l, int r){
	swap(arry[l], arry[rand() % (r - l + 1) + l]);
	T v = arry[l];

	//arry[l+1...i]<=v;  arr[j...r)>v
	int i = l + 1,j=r;
	while (true){
		while (i <= r&&arry[i] < v)i++;
		while (j >= l + 1 && arry[j]>v)j--;
		if (i > j)break;
		swap(arry[i], arry[j]);
		i++;
		j--;

	}

	swap(arry[l], arry[j]);
	return j;

}


template<typename T>
void __quickSort(T arry[], int l,int r){
	if (l >= r)
		return;

	int p = __partion2(arry, l, r);
	__quickSort(arry, l, p-1);
	__quickSort(arry, p + 1, r);
}

template<typename T>
void quickSort(T arry[], int n){
	srand(time(NULL));
	__quickSort(arry, 0, n - 1);
}

七、三路快排

处理后:

将l和lt位置处的元素进行交换

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值