冒泡排序与快速排序
1、序言
ios开发中涉及到算法的地方还真不多,除非你的应用程序真的非常大,或者你想你的应用程序性能非常好才会去想到关于算法方面的性能优化,而在ios开发中真的能用得到的也就是关于排序的,当然如果你是做游戏的话那么你可能会涉及到不少的算法或者优化问题,但是这不是本篇文章讨论的范围。
后面的文章中,我将会给大家详细介绍八大算法。
2、冒泡排序
2.1 引出
前面的两篇博客里讲的插入排序是基于“逐个记录插入”,选择排序是基于“选择”,那么冒泡排序其实是基于“交换”。每次从第一个记录开始,一、二两个记录比较,大的往后放,二三两个记录比较...依次类推,这就是一趟冒泡排序。每一趟冒泡排序后,无序序列中值最大的记录冒到序列末尾,所以称之为冒泡排序。
2.2 代码
1 //冒泡排序 2 void bubbleSort(int *a,int n) 3 { 4 int i,j; 5 for(i=1;i<n;i++) 6 for(j=1;j<n-i+1;j++){ 7 if(a[j+1]<a[j]){ 8 a[j]=a[j]+a[j+1]; 9 a[j+1]=a[j]-a[j+1]; 10 a[j]=a[j]-a[j+1]; 11 } 12 } 13 }
冒泡排序算法:
1 冒泡排序代码 2 3 static void Main(string[] args) 4 { 5 ////五次比较 6 for (int i = 1; i <= 5; i++) 7 { 8 List<int> list = new List<int>(); 9 //插入2k个随机数到数组中 10 for (int j = 0; j < 2000; j++) 11 { 12 Thread.Sleep(1); 13 list.Add(new Random((int)DateTime.Now.Ticks).Next(0, 100000)); 14 } 15 Console.WriteLine("\n第" + i + "次比较:"); 16 Stopwatch watch = new Stopwatch(); 17 watch.Start(); 18 var result = list.OrderBy(single => single).ToList(); 19 watch.Stop(); 20 Console.WriteLine("\n快速排序耗费时间:" + watch.ElapsedMilliseconds); 21 Console.WriteLine("输出前是十个数:" + string.Join(",", result.Take(10).ToList())); 22 watch.Start(); 23 result = BubbleSort(list); 24 watch.Stop(); 25 Console.WriteLine("\n冒泡排序耗费时间:" + watch.ElapsedMilliseconds); 26 Console.WriteLine("输出前是十个数:" + string.Join(",", result.Take(10).ToList())); 27 Console.ReadKey(); 28 } 29 30 } 31 32 //冒泡排序算法 33 private static List<int> BubbleSort(List<int> list) 34 { 35 int temp; 36 //第一层循环: 表明要比较的次数,比如list.count个数,肯定要比较count-1次 37 for (int i = 0; i < list.Count - 1;i++ ) 38 { 39 //list.count-1:取数据最后一个数下标,47 40 //j>i: 从后往前的的下标一定大于从前往后的下标,否则就超越了。 41 for (var j = list.Count-1; j > i;j-- ) 42 { 43 //如果前面一个数大于后面一个数则交换 44 if (list[j - 1] > list[j]) 45 { 46 temp = list[j - 1]; 47 list[j - 1] = list[j]; 48 list[j] = temp; 49 } 50 } 51 52 } 53 return list; 54 }
2.3 效率分析
相对于简单选择排序,冒泡排序交换次数明显更多。它是通过不断地交换把最大的数冒出来。冒泡排序平均时间和最坏情况下(逆序)时间为o(n^2)。最佳情况下虽然不用交换,但比较的次数没有减少,时间复杂度仍为o(n^2)。此外冒泡排序是稳定的。
3、快速排序
3.1 引出
快速排序是冒泡排序的一种改进,冒泡排序排完一趟是最大值冒出来了,那么可不可以先选定一个值,然后扫描待排序序列,把小于该值的记录和大于该值的记录分成两个单独的序列,然后分别对这两个序列进行上述操作。这就是快速排序,我们把选定的那个值称为枢纽值,如果枢纽值为序列中的最大值,那么一趟快速排序就变成了一趟冒泡排序。
3.2 代码
两种版本,第一种是参考《数据结构》,在网上这种写法很流行。第二种是参考《算法导论》,实现起来较复杂。
1 //快速排序(两端交替着向中间扫描) 2 void quickSort1(int *a,int low,int high) 3 { 4 int pivotkey=a[low];//以a[low]为枢纽值 5 int i=low,j=high; 6 if(low>=high) 7 return; 8 //一趟快速排序 9 while(i<j){//双向扫描 10 while(i < j && a[j] >= pivotkey) 11 j--; 12 a[i]=a[j]; 13 while(i < j && a[i] <= pivotkey) 14 i++; 15 a[j]=a[i]; 16 } 17 a[i]=pivotkey;//放置枢纽值 18 //分别对左边、右边排序 19 quickSort1(a,low,i-1); 20 quickSort1(a,i+1,high); 21 } 22 23 //快速排序(以最后一个记录的值为枢纽值,单向扫描数组) 24 void quickSort2(int *a,int low,int high) 25 { 26 int pivotkey=a[high];//以a[high]为枢纽值 27 int i=low-1,temp,j; 28 if(low>=high) 29 return; 30 //一趟快速排序 31 for(j=low;j<high;j++){ 32 if(a[j]<=pivotkey){ 33 i++; 34 temp=a[i]; 35 a[i]=a[j]; 36 a[j]=temp; 37 } 38 } 39 i++; 40 //放置枢纽值 41 temp=a[i]; 42 a[i]=pivotkey; 43 a[high]=temp; 44 //分别对左边、右边排序 45 quickSort2(a,low,i-1); 46 quickSort2(a,i+1,high); 47 }
快速排序算法:
1 快速排序法 2 3 static void Main(string[] args) 4 { 5 6 //5次比较 7 for (int i = 1; i <= 5; i++) 8 { 9 List<int> list = new List<int>(); 10 //插入200个随机数到数组中 11 for (int j = 0; j < 200; j++) 12 { 13 Thread.Sleep(1); 14 list.Add(new Random((int)DateTime.Now.Ticks).Next(0, 10000)); 15 } 16 Console.WriteLine("\n第" + i + "次比较:"); 17 Stopwatch watch = new Stopwatch(); 18 watch.Start(); 19 var result = list.OrderBy(single => single).ToList(); 20 watch.Stop(); 21 Console.WriteLine("\n系统定义的快速排序耗费时间:" + watch.ElapsedMilliseconds); 22 Console.WriteLine("输出前是十个数:" + string.Join(",", result.Take(10).ToList())); 23 watch.Start(); 24 new QuickSortClass().QuickSort(list, 0, list.Count - 1); 25 watch.Stop(); 26 Console.WriteLine("\n俺自己写的快速排序耗费时间:" + watch.ElapsedMilliseconds); 27 Console.WriteLine("输出前是十个数:" + string.Join(",", list.Take(10).ToList())); 28 Console.ReadKey(); 29 } 30 } 31 32 public class QuickSortClass 33 { 34 35 ///<summary> 36 ////// 分割函数 37 ///</summary> 38 //////<param name="list">待排序的数组</param> 39 ///<param name="left">数组的左下标</param> 40 //////<param name="right"></param> 41 ///<returns></returns> 42 public int Division(List<int> list, int left, int right) 43 { 44 //首先挑选一个基准元素 45 int baseNum = list[left]; 46 while (left < right) 47 { 48 //从数组的右端开始向前找,一直找到比base小的数字为止(包括base同等数) 49 while (left < right && list[right] >= baseNum) 50 right = right - 1; 51 //最终找到了比baseNum小的元素,要做的事情就是此元素放到base的位置 52 list[left] = list[right]; 53 //从数组的左端开始向后找,一直找到比base大的数字为止(包括base同等数) 54 while (left < right && list[left] <= baseNum) 55 left = left + 1; 56 //最终找到了比baseNum大的元素,要做的事情就是将此元素放到最后的位置 57 list[right] = list[left]; 58 } 59 //最后就是把baseNum放到该left的位置 60 list[left] = baseNum; 61 //最终,我们发现left位置的左侧数值部分比left小,left位置右侧数值比left大 62 //至此,我们完成了第一篇排序 63 return left; 64 } 65 public void QuickSort(List<int> list, int left, int right) 66 { 67 //左下标一定小于右下标,否则就超越了 68 if (left < right) 69 { 70 //对数组进行分割,取出下次分割的基准标号 71 int i = Division(list, left, right); 72 //对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序 73 QuickSort(list, left, i - 1); 74 //对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序 75 QuickSort(list, i + 1, right); 76 } 77 } 78 }
3.3 效率分析
快速排序时间与划分是否对称有关。快速排序的平均时间复杂度为o(n*logn),至于为什么是o(n*logn),请参考《算法导论》第7章,书中用递归树的方法阐述了快速排序平均时间。且常数因子很小,所以就平均时间而言,快速排序是很好的内部排序方法。最佳情况下(每次划分都对称)时间复杂度o(n*logn)。最坏情况下(每次划分都不对称,如输入的序列有序或者逆序时)时间复杂度为o(n^2),所以在待排序序列有序或逆序时不宜选用快速排序。此外,快速排序是不稳定的。
最佳情况下,每次划分都是对称的,由于枢纽值不再考虑,所以得到的两个子问题的大小不可能大于n/2,同时一趟快速排序时间为o(n),所以运行时间递归表达式:
T(n)<=2T(n/2)+o(n)。这个递归式的解法请参考下一篇博客中归并排序效率分析。其解为T(n)=o(n*logn)。
最坏情况下,每次划分都很不对称,T(n)=T(n-1)+o(n),可以用递归树来解,第i层的代价为n-i+1.总共有n层。把每一层代价加起来有n-1个n相加。所以这个递归式的解为T(n)=o(n^2),此时就是冒泡排序。