常用排序算法

常用算法总结

最近为了面试,在恶补算法的一些知识,算是尝到了大学没有好好学习苦头,博文是转载的,我为了加深一些映像就自己写一篇,顺便加了一些自己的理解,有错误的话希望各位能够指正。

排序算法大体可分为两种:
一种是比较排序,时间复杂度O(nlogn) ~ O(n^2),主要有:冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等。
另一种是非比较排序,时间复杂度可以达到O(n),主要有:计数排序,基数排序,桶排序等。

这里我们来探讨一下常用的比较排序算法,非比较排序算法将在下一篇文章中介绍。下表给出了常见比较排序算法的性能:
Alt

排序算法稳定性的定义:
排序算法稳定性的简单形式化定义为:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的。通俗地讲就是保证排序前后两个相等的数的相对顺序不变。
排序算法稳定性的好处
排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,前一个键排序的结果可以为后一个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位排序后元素的顺序在高位也相同时是不会改变的。

冒泡排序

这个算法就不用介绍了吧,基本每个初学者第一个接触的算法就是冒泡算法,名字的由来就是没经过一轮比较,最大或最小的数字就会像气泡一样浮到最尾段。
冒泡算法的工作顺序为:
1、比较相邻的元素,如果需要调换位置的就进行调换
2、对每个元素重复步骤一,到最后一个元素将会是最大的元素
3、返回开始继续重复上述的步骤,除了最后一个元素
4、继续重复上述的步骤,直至没有元素任何一对数字需要比较

// BubbleSort
void swap(int A, i, j) {
	int temp = A[i];
	A[i] = A[j];
	A[j] = temp;
}
void BubbleSort(int A, int n) {
	for(int i = 0; i < n-1; i++) {
		for(int j = 0; j < n-i-1; j++) { //每比完一轮,最大元素就浮到最后面,继续比较浮完元素的前面元素
			if(A[j] > A[j+1]) {
				swap(A, j, j+1);
			}
		} 
	}
}
void mian() {
	int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 };
	int n = sizeof(A) / sizeof(int);
	BubbleSort(A, n);
    printf("冒泡排序结果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
    return 0;
}

实现过程如下:
Alt

冒泡算法的优点就是易于理解,但是是很没有效率的算法(如果一个数组有n个数,那么排序完成后需要比较n*(n-1)/2次)

选择排序

选择排序也是一种简单直观的排序算法。它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
选择排序跟冒泡排序最大区别在于:冒泡排序在遍历的过程中重要符合条件都会进行交换,而选择排序则在遍历过程中记住了当前的最大或最小元素,才进行位置的交换

#include <stdio.h>

void swap(int A[], i, j) {
	int temp = A[i];
    A[i] = A[j];
    A[j] = temp;
}
void SelectSort(int A[], int n) {
	for(int i = 0; i < n-1; i++) {
		int min = i;
		for(int j = i+1; j < n-1; j++) {
			if(A[min] > A[j]) {
				min = j;
			}
		}
		if(min != i) {
			swap(A, min, i);
		}
	}
}
void mian() {
	int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 };
	int n = sizeof(A) / sizeof(int);
	SelectSort(A, n);
    printf("冒泡排序结果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
    return 0;
}

选择排序实现过程:
alt
选择排序是不稳定的算法
比如序列:{ 5, 8, 5, 2, 9 },第一次选择的最小元素是2,然后把2和第一个5进行交换,从而改变了两个元素5的相对次序。

插入排序

插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。插入排序的特点就是比较过的元素始终是排好序的。
具体算法描述如下:
1、默认第一个元素是排好序的,从第二个元素开始进行向前比较
2、与上一个元素进行比较,如果比上一个元素小的话,则向前挪一位
3、重复步骤二,直到上一个元素比该元素小
4、将该元素插入到比较元素的后面
5、重复上述步骤,直到最后一个元素

#include <stdio.h>

void InsertionSort(int A[], int n) {
	for(int i = 1; i < n; i++) { //默认第一个元素是排好序的
		int right = A[i];
		int left = i - 1;
		while(j >= 0 && right < A[left]) { //当等于的时候不进入循环,因此插入排序是稳定的
			A[left] = A[left+1]; //如果右边的元素比左边的要小,就需要换位置
			left--;
		}
		A[left+1] = A[right]; //最后将元素插入到合适的位置中
	}
}
void main() {
	int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 };// 从小到大插入排序
    int n = sizeof(A) / sizeof(int);
    InsertionSort(A, n);
    printf("插入排序结果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
    return 0;
}

上述代码对序列{ 6, 5, 3, 1, 8, 7, 2, 4 }进行插入排序的实现过程如下:
alt
插入排序是稳定的算法,在数量级较小的时候速度还是比较快的,但是当数量级多的时候,元素的移动会变得很费时间和效率。插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。

二分插入排序

因为插入排序前面比较过的元素始终是排序好的,所以后面的元素在查找插入位置的时候可以用二分查找,可以减少比较次数。

#include <stdio.h>
void InsertionSortDichotomy(int A[],  n) {
	for(int i = 1; i < n; i++) {
		int left = 0;
		int right = i-1;
		int ins = A[i];
		while(left <= right) {
			int mid = (left + right) / 2;
			if(A[mid] > ins) {
				right = mid - 1;
			} else {
				left = mid + 1;
			}
		}
		for(int j = right; j >= left; j--) {
			A[j+1] = a[j];
		}
		A[left] = ins;
	}
}
int main()
{
    int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 从小到大二分插入排序
    int n = sizeof(A) / sizeof(int);
    InsertionSortDichotomy(A, n);
    printf("二分插入排序结果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
    return 0;
}

当n较大时,二分插入排序的比较次数比直接插入排序的最差情况好得多,但比直接插入排序的最好情况要差,所当以元素初始序列已经接近升序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列

快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:

1、从序列中挑出一个元素,作为"基准"(pivot).
2、把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
3、对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。

#include <stdio.h>
void swap(int A[], i, j) {
	temp = A[i];
	A[i] = A[j];
	A[j] = temp;
}
void Partition(int A[], left, right) {
	int pivot = A[right]; //将最后一个元素假设为基准值
	int tail = left - 1;
	for(int i = left; i < right; i++) {
		if(A[i] <= pivot) {
			swap(A, ++tail, i);
		}
	}
	swap(A, ++tail, right);
	return tail;
}
void QuitSort(int A[], left, right) {
	if(left >= right) {
		return;
	}
	int pivot = Partition(A, left, right);
	QuitSort(A, left, pivot-1);
	QuitSort(A, pivit+1, right);
}
int main()
{
    int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 }; // 从小到大快速排序
    int n = sizeof(A) / sizeof(int);
    QuickSort(A, 0, n - 1);
    printf("快速排序结果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
    return 0;
}

快速排序是不稳定的排序算法,不稳定发生在基准元素与A[tail+1]交换的时刻。
比如序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 },基准元素是5,一次划分操作后5要和第一个8进行交换,从而改变了两个元素8的相对次序。

转载自: 常用排序算法总结.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值