原文出处:http://www.cnblogs.com/hfutwyy/archive/2012/04/25/2470173.html
本文中介绍的排序方法主要有以下几种:冒泡排序、选择排序、插入排序、希尔排序、归并排序、堆排序、快速排序。
排序算法之一:冒泡排序(Bubble Sort)
冒泡排序算法是可用的最慢的排序算法之一,但是是最容易理解和实现的一种排序算法。这种排序的得名是由于数值"像气泡“一样升至队列的顶端或者底端而得名,
通过多次遍历整个列,并且比较相邻的数据,如果左边的数值大于右边的数值就进行交换(升序)。
实现代码如下:
//Bubble Sort Code
public static void BubbleSort(int[] arr)
{
for(int i=0;i<arr.Length;i++)
{
for(int j = 0;j<arr.Length-i-1;j++)
{
if(arr[j]>arr[j+1])
{
int temp = arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
排序算法之二:选择排序(Selection Sort)
以数组为例(当然其他的集合类型也是一样),这种排序是从数组的起始处开始,把第一个元素与数组中其他元素进行比较。然后将最小的元素放在第0个位置,接着再从第一个位置开始再次进行排序操作。直到数组的最后一个元素为止。
//Selection Sort Code
public static void SelectSort(int[] arr)
{
int min;
for (int i = 0; i < arr.Length-1; i++)
{
min = i;
for (int j = i + 1; j < arr.Length; j++)
{
if (arr[min] > arr[j]) // 若写成if (arr[i] > arr[j])是错误的!注意!!
{
min = j; //保存最小数的下标
}
}
if (min != i)
{
int temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}
}
排序算法之三:插入排序(Insertion Sort)
其基本操作为将一个记录插入到一个已经排序好的序列中,从而得到一个新的、记录数增1的新序列。
如下图所示插入排序的过程:(图片源自网络)
//Insertion Sort Code
public static void InsertionSort(int[] arr)
{
int inner, temp;
for (int i = 1; i < arr.Length; i++)
{
temp = arr[i];
inner = i;
while (inner > 0 && arr[inner - 1] > temp)
{
arr[inner] = arr[inner - 1];
inner -= 1;
}
arr[inner - 1] = temp;
}
}
总结:
选择排序是如上三种排序算法中效率最高的一种,其次是冒泡和插入。
如上三种排序算法对小型数据集合适用,若是大型数据集合,由于其性能瓶颈,最好选用其他高级排序算法。
排序算法之四:希尔排序(Shell‘s Sort)又称“缩小增量排序”(Diminshing Increment Sort)是一种对插入排序的改进算法。
基本思想为:
设待排序记录序列有n个记录,首先取一个整数gap<n作为间隔,将全部记录分为gap个子序列,所有间隔为gap的记录放在同一子序列中,在每一子序列中分别执行直接插入排序。
重复上述的子序列划分和排序工作,直到gap==1将所有记录放在同一个序列中排序为止。
C#实现代码如下:
//Shell's Sort Code
public static void ShellSort(int[] arr)
{
int inner, temp;
int h = 3;
while (h > 0)
{
for (int outer = h; outer <= arr.Length - 1; outer ++)
{
temp = arr[outer];
inner = outer;
while ((inner > h - 1) && arr[inner - h] >= temp)
{
arr[inner] = arr[inner - h];
inner -= h;
}
arr[inner] = temp;
}
Console.WriteLine();
Console.WriteLine("h="+h.ToString());
DisplayArray(arr);
h = (h - 1) % 3;
}
}
排序算法之五:归并排序算法(Merge Sort)
归并排序法是将两个(或多个)有序集合合并成一个新的有序集合。即把待排序集合分为若干个子集合,对每个集合进行排序,然后再把这些有序的子集合合并为整体集合。是分治法(Divide and COnquer)的典型应用。
若将两个集合合并成一个集合称为2-路归并。
百度百科关于归并排序算法的介绍,非常的详细,请参阅:http://baike.baidu.com/view/19000.htm
本处以2-路归并算法为例给出C#源代码,供大家学习。
//Merge Sort Code
public static void MergeSort(int[] arr)
{
int arrLength1, arrLength2;
arrLength1 = arr.Length / 2;
if (arr.Length % 2 == 0)
{
arrLength2 = arrLength1;
}
else
{
arrLength2 = arrLength1 + 1;
}
int[] arr1 = new int[arrLength1];
int[] arr2 = new int[arrLength2];
Array.Copy(arr, 0, arr1, 0, arrLength1);
Array.Copy(arr, arrLength1, arr2, 0, arrLength2);
Console.WriteLine("\narr1 Before Sort:");
DisplayArray(arr1);
Console.WriteLine("\narr2 Before Sort:");
DisplayArray(arr2);
//应用冒泡排序法为其排序
BubbleSort(arr1);
BubbleSort(arr2);
Console.WriteLine("\narr1 After Sort:");
DisplayArray(arr1);
Console.WriteLine("\narr2 After Sort:");
DisplayArray(arr2);
//合并操作arr作为目标数组
int arrIndex = 0;
int arr1Index =0,arr2Index = 0;
while(arr1Index<arrLength1 && arr2Index < arrLength2)
{
//选择较小项存入arr
if (arr1[arr1Index] < arr2[arr2Index])
{
arr[arrIndex] = arr1[arr1Index];
arr1Index++;
}
else
{
arr[arrIndex] = arr2[arr2Index];
arr2Index++;
}
arrIndex++;
}
//对arr1或者arr2中的没有存入arr中的元素进行追加
if (arr1Index != arrLength1 )
{
while (arr1Index < arrLength1)
{
arr[arrIndex] = arr1[arr1Index];
arrIndex++;
arr1Index++;
}
}
else if (arr2Index != arrLength2 )
{
while (arr2Index < arrLength2)
{
arr[arrIndex] = arr2[arr2Index];
arrIndex++;
arr2Index++;
}
}
DisplayArray(arr);
}
归并排序算法的时间复杂度为O(nlogn),空间复杂度为O(n).
排序算法之六:堆排序(Heap Sort)
首先介绍堆的概念,如果有一个关键字集合{k0,k1,k2,...,kn-1},把其所有元素按完全二叉树的顺序存储在一个一维数组中,当且仅当ki<=K2i并且ki<=k(2i+1)称为最小堆。相反称为最大堆。
堆性质1:大顶堆的堆顶关键字肯定是所有关键字中最大的,小顶堆的堆顶关键字是所有关键字中最小的。
堆排序的思想:
堆排序的思想:利用如上堆的性质1为基础,使每次从无序中选择最大记录(或最小记录)变得简单。
构建初始堆和调整堆是堆排序的两个最重要的过程。并且初始堆也是对堆中所有元素的调整过程。所以算法的核心也正是堆的调整。
如下摘自海子的园子(http://www.cnblogs.com/dolphin0520/archive/2011/10/06/2199741.html)
下面举例说明:
给定一个整形数组a[]={16,7,3,20,17,8},对其进行堆排序。
首先根据该数组元素构建一个完全二叉树,得到
20和16交换后导致16不满足堆的性质,因此需重新调整
这样就得到了初始堆。











//Heap Sort Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace HeapSort
{
class Program
{
static void Main(string[] args)
{
int[] arr = {53,17,78,09,45,65,87,23};
//调整初始数据为最小堆(或者最大堆)
Heap.MinHeapAdjust(arr,0, arr.Length);
//交换首尾数字输出排序结果
for (int i = arr.Length-1; i >= 0; i--)
{
//交换首尾
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//调整除尾部之后的特定的堆
Heap.MinHeapAdjust(arr, 0, i);
}
Heap.DisplayHeap(arr);
Console.ReadLine();
}
}
public static class Heap
{
/// <summary>
/// 将传入的数组调节为最小堆
/// </summary>
/// <param name="arr">待调解数组</param>
/// <param name="maxIndex">待调节的最大索引,最大索引之后的数据将不会调节</param>
public static void MinHeapAdjust(int[] arr,int startIndex, int maxIndex)
{
//Console.WriteLine("Before Adjust");
//DisplayHeap(arr);
//首先需要获得倒数第一个分枝节点
int currentBranchIndex = maxIndex / 2-1;
//调整分枝节点及其子节点
for (; currentBranchIndex >= startIndex; currentBranchIndex--)
{
AdjustSubHeap(arr, currentBranchIndex, maxIndex);
}
//Console.WriteLine("After Adjust");
//DisplayHeap(arr);
}
/// <summary>
/// FliterDown算法,调整以startIndex为要的子树为最小堆
/// </summary>
/// <param name="arr"></param>
/// <param name="currentIndex"></param>
/// <param name="maxIndex"></param>
public static void AdjustSubHeap(int[] arr, int currentIndex, int maxIndex)
{
var temp = arr[currentIndex];
if(2 * currentIndex + 1<maxIndex)
if (temp > arr[2 * currentIndex + 1])
{
arr[currentIndex] = arr[2 * currentIndex + 1];
arr[2 * currentIndex + 1] = temp;
temp = arr[currentIndex]; //若本子树顶部的键值已经改变,则将此键值存入临时区
AdjustSubHeap(arr, 2 * currentIndex + 1, maxIndex);
}
if (2 * currentIndex + 2 < maxIndex) //如果有最后一个节点有两个子节点
{
if (temp > arr[2 * currentIndex + 2])
{
arr[currentIndex] = arr[2 * currentIndex + 2];
arr[2 * currentIndex + 2] = temp;
}
AdjustSubHeap(arr, 2 * currentIndex + 2, maxIndex);
}
}
/// <summary>
/// 显示Heap
/// </summary>
/// <param name="arr"></param>
public static void DisplayHeap(int[] arr)
{
foreach(int val in arr)
{
Console.WriteLine(val + " ");
}
}
}
}
排序算法之七:快速排序(quick sort)
快速排序也是分治法的一个实例,其主要思想为,选取一个值,将大于其的值放在其后,小于其的值放在其前。并对其前和其后的数据段进行迭代,直至分到数据段为一个数字。
【它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。】(如上括号里面的为引用自别处,人家写的比俺写的好多了。)
其具体做法如下:
设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用第一个数据)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。
//Quick Sort Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace QuickSort
{
class Program
{
static void Main(string[] args)
{
int[] arr = { 97,76,13,27,20,10,9,90,49,65};
QuickSort.sort(arr, 0, arr.Length-1);
QuickSort.DisplayArr(arr, 0, arr.Length);
Console.ReadLine();
}
}
public static class QuickSort
{
public static void sort(int[] arr,int lowIndex,int upperIndex)
{
if (lowIndex >= upperIndex) return;
int key,i,j,temp;
i = lowIndex;
key = arr[lowIndex];
j = upperIndex;
bool leftToRight = false;
while (i< j)
{
//从右向左检索,查找右边第一个小于key的元素
if(!leftToRight)
if (arr[j] < key)
{
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
leftToRight = true;
i = lowIndex;
}
else
{
j--;
}
//从左向右检索,查找左边第一个大于key的元素
if (leftToRight)
if (arr[i] > key)
{
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
j = upperIndex;
leftToRight = false;
}
else
{
i++;
}
}
if (lowIndex < upperIndex)
{
sort(arr, lowIndex, i-1);
sort(arr, i + 1, upperIndex);
}
}
//输出数组
public static void DisplayArr(int[] arr, int lowindex, int upperindex)
{
Console.WriteLine();
for (int i = lowindex; i < upperindex; i++)
{
Console.Write(arr[i] + " ");
}
}
}
}
排序方法的选择
因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法很重要
(1)若n较小,可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好,它会比选择更少的比较次数;
但当记录规模较大时,因为直接选择移动的记录数少于直接插人,所以宜用选直接选择排序。
这两种都是稳定排序算法。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜(这里的随机是指基准取值的随机,原因见上的快速排序分析);这里快速排序算法将不稳定。
(3)若n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序虽不会出现快速排序可能出现的最坏情况。但它需要建堆的过程。这两种排序都是不稳定的。
归并排序是稳定的排序算法,但它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。
(4)特殊的箱排序、基数排序
它们都是一种稳定的排序算法,但有一定的局限*:
1>关键字可分解。
2>记录的关键字位数较少,如果密集更好
3>如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。