YUAN 2019-07-01
<img src="https://www.cnblogs.com/image...; width="100%" hegiht="20%" align=center />
关于排序,江湖盛传有一种分治思想,能大幅度提高排序心法的性能。所谓分治,即:化大为小,分而治之。达到治小而治大的成效。多年来基于分治思想衍生出多种排序心法,然万变不离其宗!虽然江湖上算法内功繁多,但是好的算法小编认为必须符合以下几个条件,方能真正提高习练者实力。
在算法时间复杂度维度,我们主要对比较和交换的次数做对比,其他不交换元素的算法,主要会以访问数组的次数的维度做对比。
其实有很多修炼者对于算法的时间复杂度有点模糊,分不清什么所谓的 O(n),O(nlogn),O(logn)...等,也许下图对一些人有一些更直观的认识。
排序算法的额外内存开销和运行时间同等重要。 就算一个算法时间复杂度比较优秀,空间复杂度非常差,使用的额外内存非常大,菜菜认为它也算不上一个优秀的算法。
这个指标是菜菜自己加上的,我始终认为一个优秀的算法最终得到的结果必须是正确的。就算一个算法拥有非常优秀的时间和空间复杂度,但是结果不正确,导致修炼者经脉逆转,走火入魔,又有什么意义呢?
基本思想:选取一个元素作为分割点,通过遍历把小于分割点的元素放到分割点左边,把大于分割点的元素放到分割点元素右边。然后再按此方法对两部分数据分别排序,以此类推,直到分割的数组大小为1。 整个排序过程可以递归进行,以此达到整个数据变成有序序列。
实现快速排序的方式有很多,其中以类似指针移动方式最为常见,为什么最常见呢?因为它的空间复杂度为O(1),也就是说是原地排序。
{23 58 13 10 57 62} 65 {106 78 95 85} {10 13} 23 {58 57 62} 65 {85 78 95} 106 10 13 23 57 58 62 65 78 85 95 106
关于复杂度相关O(n)等公式,我这里需要强调一点,公式代表的是算法的复杂度增长的趋势,而不是具体计算复杂度的公式。比如:O(n²)和O(n)相比较,只是说明 O(n²)增长的趋势要比o(n)快,并不是说明O(n²)的算法比O(n)的算法所用时间一定就要多。
快速排序平均时间复杂度为O(nlogn),最好情况下为O(nlogn),最坏情况下O(n²)
基于以上例子来实现的快排,空间复杂度为O(1),也就是原地排序。
举个例子:
待排序数组:int a[] ={1, 2, 2, 3, 4, 5, 6};
在快速排序的随机选择比较子(即pivot)阶段:
若选择a[2](即数组中的第二个2)为比较子,,而把大于等于比较子的数均放置在大数数组中,则a[1](即数组中的第一个2)会到pivot的右边, 那么数组中的两个2非原序(这就是“不稳定”)。
若选择a[1]为比较子,而把小于等于比较子的数均放置在小数数组中,则数组中的两个2顺序也非原序。可见快速排序不是稳定的排序。
通过以上分析各位侠士是否能够分析出来快速排序有哪些地方存在瑕疵呢?
static void Main(string[] args) { List<int> data = new List<int>(); for (int i = 0; i < 11; i++) { data.Add(new Random(Guid.NewGuid().GetHashCode()).Next(1, 100)); } //打印原始数组值 Console.WriteLine($"原始数据: {string.Join(",", data)}"); quickSort(data, 0, data.Count - 1); //打印排序后的数组 Console.WriteLine($"排序数据: {string.Join(",", data)}"); Console.Read(); } public static void quickSort(List <int> source, int left, int right) { int pivot = 0; if (left < right) { pivot = partition(source, left, right); quickSort(source, left, pivot - 1); quickSort(source, pivot + 1, right); } } //对一个数组/列表按照第一个元素 分组排序,返回排序之后key所在的位置索引 private static int partition(List<int> source, int left, int right) { int key = source[left]; while (left < right) { //从右边筛选 大于选取的值的不动,小于key的交换位置 while (left < right && source[right] >= key) { right--; } source[left] = source[right]; while (left < right && source[left] <= key) { left++; } source[right] = source[left]; } source[left] = key; return left; }
package main import ( "fmt" "math/rand" ) func main() { var data []int for i := 0; i < 10; i++ { data = append(data, rand.Intn(100)) } fmt.Println(data) quickSort(data[:], 0, len(data)-1) fmt.Println(data) } func quickSort(source []int, left int, right int) { var pivot = 0 if left < right { pivot = partition(source, left, right) quickSort(source, left, pivot-1) quickSort(source, pivot+1, right) } } func partition(source []int, left int, right int) int { var key = source[left] for left < right { for left < right && source[right] >= key { right-- } source[left] = source[right] for left < right && source[left] <= key { left++ } source[right] = source[left] } source[left] = key return left }
运行结果:
[81 87 47 59 81 18 25 40 56 0] [0 18 25 40 47 56 59 81 81 87]
添加关注,查看更精美版本,收获更多精彩