排序之交换排序(冒泡排序、快速排序)
冒泡排序
阐述:
通过比较相邻两个元素的值,将最小值(假设从小到大排序)置换到待排集合的首位,通俗说就是把最小值放到待排集合的前面。
冒排包括两个遍历,确定待排集合的首位置,遍历待排集合置换出最小值,所以时间复杂度是O(N*N)。
冒排的是两个相邻元素的位置交换,相邻操作很显然不会打乱相同元素的相对位置,故而是稳定的。
冒排关键字:临近元素两两比较。
效果图:
代码(c#):
/// <summary> /// 冒泡排序 /// </summary> public static void DoBubblingSort() { List<int> listNum = new List<int>() { 3, 10, 11, 2, 6, 4, 2, 17, 19 }; LogWrite.LogPlain(DisplayList(listNum)); for (int i = 0; i < listNum.Count - 1; i++) //索引 i 用于定位待排集合的首位置 { for (int j = listNum.Count - 1; j > i; j--) //遍历待排集合,通过临近的元素两两交换,把最小值置换到待排集合的首位 { if (listNum[j - 1] > listNum[j]) { //交换临近元素的值 Swap(listNum, j - 1, j); //LogWrite.LogPlain(DisplayList(listNum, new List<int> { j - 1, j })); } } } } /// <summary> /// 交换数组两个元素的值 /// </summary> /// <param name="listNum"></param> /// <param name="indexSource"></param> /// <param name="indexTarget"></param> public static void Swap(List<int> listNum, int index1, int index2) { int swapTemp = listNum[index1]; listNum[index1] = listNum[index2]; listNum[index2] = swapTemp; }
快速排序:
阐述:
快排通过取中值获得一个基准数(获得基准的方法还有其他),通过一趟排序使得基准的左边元素小于基准值,基准右边的元素大于基准值,然后对由基准分开的两个部分继续执行上述操作,递归到处理对象集合数为1停止(递归操作有点像深度优先一样,一定要走到处理对象集合数为1才会停止递归)。
同样是交换元素位置获得有序的集合,快排比起冒排效率高多了,根源还是来自神奇的递归操作,递归操作是分治思想的实现方式之一,递归的过程就是遍历二叉树的过程,而树的每一层操作相对独立、隔离,比起冒排会大大减少冗余的比较,所以效率相对很高。
快排在每一层的处理中,在置换基准时,可能使得相同元素的相对位置发生变化,索引快排是不稳定的。
快排关键字:基准,递归。
时间复杂度:
快排的基准,如果经过一趟排序都能落在集合的中间位置,这是最好的情况了,因为这样下去递归的轨迹会呈现出一个枝叶丰满的二叉树(满二叉树或者完全二叉树),然后树深度到达一个理想值lgN,树深度越大,需要递归的次数就越多,很显然性能就会下降,另外,树的每一层置换的时间复杂度是O(N),所以在最好的情况下快排的时间复杂度是O(N*lgN)。
最不幸运的情况下,快排的基准经过一趟排序落在集合的边缘位置,这样下去递归的轨迹会呈现出一个很单调的二叉树(专业点说就是退化成一个没有分支的二叉树,树高变成了N),也就是需要进行N趟递归才能停止,复杂度就是O(N),所以在最坏的情况下快排的时间复杂度是O(N*N)。
树的深度由lgN退化到了N,这是呈指数级的退化啊,还是很可观的退化。
算法描述:
1.取中值作为基准,使得基准跑到数组合适的位置(左边的小于基准值,右边的大于基准值)
2.处理基准的左部(若有),重新走第一步,直到集合数为1
3.处理基准的右部(若有),重新走第一步,直到集合数为1
4.结束
效果图:
代码(c#):
/// <summary> /// 快速排序方法入口 /// </summary> public static void DoQuickSort_Entrance() { List<int> listNum = new List<int>() { 3, 10, 19, 20, 6, 4, 22, 17, 19 }; DoQuickSort_Sort(listNum, 0, listNum.Count - 1); } /// <summary> /// 快速排序实现 /// </summary> public static void DoQuickSort_Sort(List<int> listNum, int indexLeft, int indexRight) { //检查索引 if (!(indexLeft > -1 && indexRight < listNum.Count && indexRight >= indexLeft)) return; int start = indexLeft; int end = indexRight; int pivotKey = listNum[(indexLeft + indexRight) / 2];//通过取中值获取基准数 //LogWrite.LogPlain(DisplayList(listNum, new List<int>() { (indexLeft + indexRight) / 2,indexLeft,indexRight }, '<', '>')); //左索引和右索引向基准遍历 while (indexLeft <= indexRight) { //获取左半部比基准大的元素 while (listNum[indexLeft] < pivotKey && indexLeft < end) { indexLeft++; } //获取右半部比基准小的元素 while (listNum[indexRight] > pivotKey && indexRight > start) { indexRight--; }; if (indexLeft <= indexRight) { //交换上面两个元素的相对位置 Swap(listNum, indexLeft, indexRight); //LogWrite.LogPlain(DisplayList(listNum, new List<int>() { indexLeft, indexRight })); //使得左右索引继续向基准逼近 indexLeft++; indexRight--; } } //基准将集合一分为二,分别递归处理左半部分和右半部分 if (start < indexRight) DoQuickSort_Sort(listNum, start, indexRight); if (indexLeft < end) DoQuickSort_Sort(listNum, indexLeft, end); } /// <summary> /// 交换数组两个元素的值 /// </summary> /// <param name="listNum"></param> /// <param name="indexSource"></param> /// <param name="indexTarget"></param> public static void Swap(List<int> listNum, int index1, int index2) { int swapTemp = listNum[index1]; listNum[index1] = listNum[index2]; listNum[index2] = swapTemp; }