【24】六大常用排序算法

  • Post author:
  • Post category:其他






一. 冒泡排序


1. 思想:利用比较相邻的两个元素,发现两个数前者大于后者则进行交换,这样每一轮可以把最大数放到后面,只要做n轮便可以使得序列有序。


2. 举例,例如序列 8 7 3 4 5 0 1


第一轮:8 7 3 4 5 0 1



8 7

3 4 5 0 1 -> 7 8 3 4 5 0 1


7

8 3

4 5 0 1 -> 7 3 8 4 5 0 1


7 3

8 4

5 0 1 -> 7 3 4 8 5 0 1


7 3 4

8 5

0 1 -> 7 3 4 5 8 0 1


7 3 4 5

8 0

1 -> 7 3 4 5 0 8 1


7 3 4 5 0

8 1

-> 7 3 4 5 0 1

8  // 得到最大值8在最后一个位置


第二轮:7 3 4 5 0 1


………..


3. 从上面的例子我们可以知道,冒泡排序的时间复杂度为O(n^2),不需要额外的空间辅助,是一个稳定的算法


4. 示例代码


#include<iostream>
#include<algorithm>
using namespace std;

//冒泡排序
void BubbleSort(int *arrNum, int n){
	//数据不合法
	if(arrNum == NULL || n <= 0){
	    return;
	}
	for(int i = 0; i < n; i++){
		for(int j = 0; j < (n-i-1); j++){
			if(arrNum[j] > arrNum[j+1]){
			    swap(arrNum[j], arrNum[j+1]);
			}
		}
	}
}

int main(){
	//test
	int arrNum[] = {8,7,3,4,5,0,1};
	BubbleSort(arrNum, 7);
	for(int i = 0; i < 7; i++){
	    cout<<arrNum[i]<<" ";
	}
	cout<<endl;
	//输出 0 1 3 4 5 7 8
	getchar();
	return 0;
}



二. 插入排序


1. 思想:每次选择未排序序列的第一个数插入已经排好序的序列中,使得这个序列依然有序


2. 举例:

例如序列 8 7 3 4 5 0 1


第一次:未排序序列为8 7 3 4 5 0 1,第一个数是8,没有排好的序列,因此排好序列为8


第二次:未排序序列为7 3 4 5 0 1,第一个数7,排好序序列为8,则插入到8前面,序列为7 8


第三次:未排序序列为3 4 5 0 1,第一个数为3,排好序序列为3,则插入到7前面,序列为3 7 8


……..


3. 只要把所有的未排序的数全部插入到已经排好序的序列中,这样整个序列就是有序的。时间复杂度为O(n^2),是一个稳定的排序算法


4. 示例代码


#include<iostream>
#include<algorithm>
using namespace std;

//插入排序
void InsertSort(int *arrNum, int n){
	//数据不合法
	if(arrNum == NULL || n <= 0){
	    return;
	}
	for(int i = 1; i < n; i++){
	    int tmp = arrNum[i];
		int j;
		for(j = i-1; j >= 0; j--){
			//如果比tmp大把值往后移动一位
			if(arrNum[j] > tmp){
			   arrNum[j+1] = arrNum[j];
			}
			else{
			   break;
			}
		}
		arrNum[j+1] = tmp;
	}	
}

int main(){
	//test
	int arrNum[] = {8,7,3,4,5,0,1};
	InsertSort(arrNum, 7);
	for(int i = 0; i < 7; i++){
	    cout<<arrNum[i]<<" ";
	}
	cout<<endl;
	//输出 0 1 3 4 5 7 8
	getchar();
	return 0;
}



三. 选择排序


1. 思想:每次从未排序的序列中选择一个最小的数未排序序列第一个数交换,相当于每次选择一个最小的数放到排好序的序列最后一个位子,当未排序序列为空的时候整个序列即为有序


2. 举例:例如序列8 7 3 4 5 0 1


第一次:序列 8 7 3 4 5 0 1,未排序序列最小的数0,和8交换,此时排好序序列为0


第二次:序列

0

7 3 4 5 8 1,未排序序列最小的数1,和7交换,此时排好序序列为0 1


第三次:序列

0 1

3 4 5 8 7,未排序序列最小的数3,本身不用交换,此时排好序序列为0 1 3


……………


3. 当未排序序列为空的时候说明整个序列为有序,时间复杂度为O(n^2),是一个不稳定的排序算法


4. 示例代码


#include<iostream>
#include<algorithm>
using namespace std;

//插入排序
void SelectSort(int *arrNum, int n){
	//数据不合法
	if(arrNum == NULL || n <= 0){
	    return;
	}
	for(int i = 0; i < n; i++){
		int minNumIndex = i;
		for(int j = i+1; j < n; j++){
			if(arrNum[j] < arrNum[minNumIndex]){
			    minNumIndex = j;
			}
		}
		//找到最小值直接和未排序第一个数交换
		if(i != minNumIndex){
		    swap(arrNum[i], arrNum[minNumIndex]);
		}
	}
}

int main(){
	//test
	int arrNum[] = {8,7,3,4,5,0,1};
	SelectSort(arrNum, 7);
	for(int i = 0; i < 7; i++){
	    cout<<arrNum[i]<<" ";
	}
	cout<<endl;
	//输出 0 1 3 4 5 7 8
	getchar();
	return 0;
}



四. 快速排序


1. 思想:利用一个基准arrNum[k]每次把区间[l,r]划分成两个部分[l,k-1],[k+1,r]使得[l,k-1]这个子区间的所有值全部小于等于arrNum[k],[k+1,r]这个区间的值全部大于等于arrNum[k]的值,然后递归对这两个子区间进行排序。


2. 举例:例如序列8 7 3 4 5 0 1,每次选择区间中间那个数作为基准


第一次:区间为[0, 6],选择基准为arrNum[3] = 4


(1)先把基准4和最后一个数交换,得到8 7 3 1 5 0

4


(2)设置两个指针p1和p2,p1用来枚举所有数,p2用来保存左子区间全部小于基准的下标,初始化p1和p2都是指向区间左端点


(3)手动推算结果如下






3. 快速排序时间复杂度为O(NlogN),是一个不稳定的算法,有可能会退化为O(n^2),不需要额外的空间


4. 示例代码


#include<iostream>
#include<algorithm>
using namespace std;

//划分区间
int Partition(int *arrNum , int l, int r){
	//左子区间结束下标
	int leftSeqIndex = l;
	int mid = (l+r)>>1;
	//把基准值交换到最后一个位置
	swap(arrNum[mid], arrNum[r]);
	//O(n)时间划分
	for(int i = l; i < r; i++){
		if(arrNum[i] < arrNum[r]){
			if(leftSeqIndex < i){
			    swap(arrNum[leftSeqIndex], arrNum[i]);
			}
			++leftSeqIndex;
		}
	}
	//把基准交换回原来的位置
	swap(arrNum[leftSeqIndex], arrNum[r]);
	return leftSeqIndex;
}

//快速排序
void QuickSort(int *arrNum, int l, int r){
	//数据不合法
	if(arrNum == NULL || l >= r){
	    return;
	}
	int index = Partition(arrNum, l, r);
	if(l < index){
		QuickSort(arrNum, l, index-1);
	}
	if(index < r){
	    QuickSort(arrNum, index+1, r);
	}
}

int main(){
	//test
	int arrNum[] = {8,7,3,4,5,0,1};
	QuickSort(arrNum, 0, 6);
	for(int i = 0; i < 7; i++){
	    cout<<arrNum[i]<<" ";
	}
	cout<<endl;
	//输出 0 1 3 4 5 7 8
	getchar();
	return 0;
}





五. 归并排序


1. 思想:利用分治的思想,每次把要排序的区间平均划分成两部分,然后递归排序,再合并这两个区间


2. 举例:例如序列8 7 3 1 5 0 4





3. 归并排序的时间复杂度为O(NlogN),是一个稳定的排序算法,但是需用一个和原来一样大的内存空间


4. 示例代码


#include<iostream>
#include<algorithm>
using namespace std;

const int N = 10;

//合并
void Merge(int *arrNum , int l, int mid, int r){
	int i = l;
	int j = mid+1;
	int pos = l;
	int tmpArrNum[N];
	while(i <= mid && j <= r){
		if(arrNum[i] <= arrNum[j]){
		   tmpArrNum[pos++] = arrNum[i++];
		}
		else{
		   tmpArrNum[pos++] = arrNum[j++];
		}
	}
	//把没有排完的数
	while(i <= mid){
	    tmpArrNum[pos++] = arrNum[i++];
	}
	while(j <= r){
	    tmpArrNum[pos++] = arrNum[j++];
	}
	//把数据拷贝回到原来数组
	for(int i = l; i <= r; i++){
	    arrNum[i] = tmpArrNum[i];
	}
}

//归并排序
void MergeSort(int *arrNum, int l, int r){
	//数据不合法
	if(arrNum == NULL || l >= r){
	    return;
	}
	int mid = (l+r)>>1;
	MergeSort(arrNum, l, mid);
	MergeSort(arrNum, mid+1, r);
	Merge(arrNum, l, mid, r);
}

int main(){
	//test
	int arrNum[] = {8,7,3,4,5,0,1};
	MergeSort(arrNum, 0, 6);
	for(int i = 0; i < 7; i++){
	    cout<<arrNum[i]<<" ";
	}
	cout<<endl;
	//输出 0 1 3 4 5 7 8
	getchar();
	return 0;
}



六. 堆排序


1. 堆排序利用堆的性质,最小堆满足根结点的值比左右子树都小,最大堆满足根结点的值比左右子树都大


2. 一个无序系列建立最小堆方法是,从第一个叶子到根结点,每个结点往下调整,调整完所有结点之后即为最小堆


为什么叶子结点不用调整?因为叶子结点已经满足最小堆的性质


3. 一个无序序列调整为最小堆的过程,序列为8 7 3 4 5 0 1





4. 建立最小堆的时间复杂度为O(n),很多人认为是O(NlogN),为什么不是呢?证明如下


假设堆中有N个元素,则树高为H = logN,对于树高为h的结点建堆时间复杂度为O(H-h)。因此从最底层到根结点所有结点的建堆时间复杂度和为

S = 0*2^(H-1)+1*2^(H-2)+2*2^(H-3)+……..+(H-1)*2^0

<= 2^(H)+2^(H-1)+2^(H-2)+…….+2^0

<= 2^(H+1)

因为H = logN,则2^(H+1) = 2^H*2 = 2N,则时间复杂度为O(N)。


5. 建立最小堆代码


//向下调整
void AdjustDown(int *arrNum, int pos, int n){
	int minIndex = pos;
	int lsonIndex = pos<<1;
	int rsonIndex = (pos<<1)+1;
	if(lsonIndex <= n && arrNum[lsonIndex] < arrNum[minIndex]){
	    minIndex = lsonIndex;
	}
	if(rsonIndex <= n && arrNum[rsonIndex] < arrNum[minIndex]){
	    minIndex = rsonIndex;
	}
	if(minIndex != pos){
	    swap(arrNum[pos], arrNum[minIndex]);
		AdjustDown(arrNum, minIndex, n);
	}
}

//建立最小堆
void BuildHeap(int *arrNum, int n){
	if(arrNum == NULL || n <= 0){
	    return;
	}
	//下标从1开始第一个非叶子结点编号为n/2
	for(int i = n/2; i >= 1; i--){
	    //向下调整
		AdjustDown(arrNum, i, n);
	}
}

6. 堆排序只需要每次把最小堆的根结点值取出并删除根结点,然后把最后一个叶子结点补到根结点并删除指,然后从根结点往下调整即可,时间复杂为O(NlogN),是一个不稳定的排序算法,不需要额外空间





#include<iostream>
#include<algorithm>
using namespace std;

//向下调整
void AdjustDown(int *arrNum, int pos, int n){
	int minIndex = pos;
	int lsonIndex = pos<<1;
	int rsonIndex = (pos<<1)+1;
	if(lsonIndex <= n && arrNum[lsonIndex] < arrNum[minIndex]){
	    minIndex = lsonIndex;
	}
	if(rsonIndex <= n && arrNum[rsonIndex] < arrNum[minIndex]){
	    minIndex = rsonIndex;
	}
	if(minIndex != pos){
	    swap(arrNum[pos], arrNum[minIndex]);
		AdjustDown(arrNum, minIndex, n);
	}
}

//建立最小堆
void BuildHeap(int *arrNum, int n){
	if(arrNum == NULL || n <= 0){
	    return;
	}
	//下标从1开始第一个非叶子结点编号为n/2
	for(int i = n/2; i >= 1; i--){
	    //向下调整
		AdjustDown(arrNum, i, n);
	}
}

void HeapSort(int *arrNum, int n){
	//不合法数据
	if(arrNum == NULL || n <= 0){
	   return;
	}
	int pos = 1;
	while(pos <= n){
		//每次输出根节点的值即为最小
	    cout<<arrNum[1]<<" ";   
		//把最后一个结点交换上去
		swap(arrNum[1], arrNum[n-pos+1]);
		//从根结点向下调整
		AdjustDown(arrNum, 1, n-pos);
		++pos;
	}
}

int main(){
	//test
	int arrNum[] = {-1, 8,7,3,4,5,0,1};
	BuildHeap(arrNum, 7);
	HeapSort(arrNum, 7);
	//输出 0 1 3 4 5 7 8
	getchar();
	return 0;
}


总结:


稳定排序算法:冒泡排序、插入排序、归并排序、基数排序


不稳定排序算法:选择排序、快速排序、堆排序、希尔排序





版权声明:本文为chenguolinblog原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。