一、选择排序
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位置处的元素进行交换