那么,首先是我们所熟悉的各种排序的时间复杂度和空间复杂度
排序法 |
最差时间分析 | 平均时间复杂度 | 稳定度 | 空间复杂度 |
冒泡排序 | O(n2) | O(n2) | 稳定 | O(1) |
快速排序 | O(n2) | O(n*log2n) | 不稳定 | O(log2n)~O(n) |
选择排序 | O(n2) | O(n2) | 稳定 | O(1) |
二叉树排序 | O(n2) | O(n*log2n) | 不一顶 | O(n) |
插入排序 |
O(n2) | O(n2) | 稳定 | O(1) |
堆排序 | O(n*log2n) | O(n*log2n) | 不稳定 | O(1) |
希尔排序 | O | O | 不稳定 | O(1) |
接下来我们逐个排序算法来Mark。
一、冒泡排序
冒泡排序的定义:
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端,故名。
由于冒泡排序简洁的特点,它通常被用来对于计算机程序设计入门的学生介绍算法的概念。
冒泡排序的实现:
每一轮都是实现一下步骤直至排序完成
1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3、针对所有的元素重复以上的步骤,除了最后一个。
4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
冒泡排序的代码:
1 static void sort(int[] a) { 2 int temp = 0; 3 boolean isSure; 4 for (int i = a.length - 1; i > 0; --i) { 5 isSure = true; 6 for (int j = 0; j < i; ++j) { 7 if (a[j + 1] < a[j]) { 8 temp = a[j]; 9 a[j] = a[j + 1]; 10 a[j + 1] = temp; 11 isSure = false; 12 } 13 } 14 if(isSure) 15 break; 16 } 17 }
二、快速排序
快速排序的定义:
快速排序(Quicksort)是对冒泡排序的一种改进。由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
快速排序的实现:
设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。
一趟快速排序的算法是:
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]赋给A[i];
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]赋给A[j];
5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[j]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。
快速排序的代码:
1 public static void sort(int a[], int start, int end){ 2 int i,j; 3 i = start; 4 j = end; 5 if((a==null)||(a.length==0)) 6 return; 7 while(i<j){ 8 while(i<j&&a[i]<=a[j]){ //以数组start下标的数据为key,右侧扫描 9 j--; 10 } 11 if(i<j){ //右侧扫描,找出第一个比key小的,交换位置 12 int temp = a[i]; 13 a[i] = a[j]; 14 a[j] = temp; 15 } 16 while(i<j&&a[i]<a[j]){ //左侧扫描(此时a[j]中存储着key值) 17 i++; 18 } 19 if(i<j){ //找出第一个比key大的,交换位置 20 int temp = a[i]; 21 a[i] = a[j]; 22 a[j] = temp; 23 } 24 } 25 if(i-start>1){ 26 //递归调用,把key前面的完成排序 27 sort(a,start,i-1); 28 } 29 if(end-i>1){ 30 sort(a,i+1,end); //递归调用,把key后面的完成排序 31 } 32 }
以下是快速排序的非递归实现
1 static void method2(int[] nums){ 2 int[] marks = new int[nums.length*2]; 3 marks[0] = 0; 4 marks[1] = nums.length -1; 5 int a=0,b=1; //a为任务执行的标识,b为新增任务的标识 6 int i=0,j=0; //i和j作为任务执行中的上标和下标 7 int temp; //temp为临时交换时用的变量 8 while(a<b){ 9 i = marks[2*a]; 10 j = marks[2*a+1]; 11 while(i<j){ 12 while(i<j&&nums[i]<=nums[j]) 13 j--; 14 if(i<j){ 15 temp = nums[i]; 16 nums[i] = nums[j]; 17 nums[j] = temp; 18 } 19 while(i<j&&nums[i]<nums[j]) 20 i++; 21 if(i<j){ 22 temp = nums[i]; 23 nums[i] = nums[j]; 24 nums[j] = temp; 25 } 26 } 27 if(i-marks[2*a]>1){ 28 marks[2*b] = marks[2*a]; 29 marks[2*b+1] = i-1; 30 b++; 31 } 32 if(marks[2*a+1]-i>1){ 33 marks[2*b] = i+1; 34 marks[2*b+1] = marks[2*a+1]; 35 b++; 36 } 37 a++; 38 } 39 System.out.println(nums.length+":"+2*b); 40 }
三、选择排序
选择排序的定义:
每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。
选择排序的实现:
对比数组中前一个元素跟后一个元素的大小,如果后面的元素比前面的元素小则用一个变量k来记住他的位置,接着第二次比较,前面“后一个元素”现变成了“前一个元素”,继续跟他的“后一个元素”进行比较如果后面的元素比他要小则用变量k记住它在数组中的位置(下标),等到循环结束的时候,我们应该找到了最小的那个数的下标了,然后进行判断,如果这个元素的下标不是第一个元素的下标,就让第一个元素跟他交换一下值,这样就找到整个数组中最小的数了。然后找到数组中第二小的数,让他跟数组中第二个元素交换一下值,以此类推。
选择排序的代码:
1 public static void sort(int a[]){ 2 int i,j,tmp,b; 3 for(i=0;i<a.length-1;i++) 4 { 5 tmp=i; 6 for(j=i+1;j<a.length;j++) 7 if(a[tmp]>a[j]) 8 tmp=j; 9 if(i!=tmp) 10 { 11 b=a[tmp]; 12 a[tmp]=a[i]; 13 a[i]=b; 14 } 15 } 16 }
四、插入排序
插入排序的定义:
有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外,而第二部分就只包含这一个元素。在第一部分排序后,再把这个最后元素插入到此刻已是有序的第一部分里的位置
插入排序的实现:
⒈从有序数列和无序数列{a2,a3,…,an}开始进行排序;
⒉处理第i个元素时(i=2,3,…,n),数列{a1,a2,…,ai-1}是已有序的,而数列{ai,ai+1,…,an}是无序的。用ai与ai-1,a i-2,…,a1进行比较,找出合适的位置将ai插入;
⒊重复第二步,共进行n-i次插入处理,数列全部有序。
插入排序的代码:
1 public static void insertSort(int[] a) { 2 for (int index = 1; index < a.length; index++) { 3 int subIndex = index; 4 int currentData = a[index]; 5 while ((subIndex > 0) && (a[subIndex - 1] > currentData)) { 6 a[subIndex] = a[subIndex - 1]; 7 subIndex--; 8 a[subIndex] = currentData; 9 } 10 } 11 }
五、堆排序
堆排序的定义:
有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外,而第二部分就只包含这一个元素。在第一部分排序后,再把这个最后元素插入到此刻已是有序的第一部分里的位置
堆排序的实现:
⒈从有序数列和无序数列{a2,a3,…,an}开始进行排序;
⒉处理第i个元素时(i=2,3,…,n),数列{a1,a2,…,ai-1}是已有序的,而数列{ai,ai+1,…,an}是无序的。用ai与ai-1,a i-2,…,a1进行比较,找出合适的位置将ai插入;
⒊重复第二步,共进行n-i次插入处理,数列全部有序。
堆排序的代码:
1 public static void sort(int[] heap){ 2 int temp; 3 /* 4 * 创建堆(对该堆进行简单的排序) 5 */ 6 for (int i = heap.length - 1; i >= 0; i--) { 7 AdjustHeap(heap,i,heap.length); 8 } 9 /* 10 * 排序 11 */ 12 for (int i = heap.length - 1; 0 < i; i--) { 13 temp = heap[0]; 14 heap[0] = heap[i]; 15 heap[i] = temp; 16 /* 17 * 从堆顶进行调整,使未排序堆中最大关键字到堆顶 18 */ 19 AdjustHeap(heap,0,i); 20 } 21 } 22 23 24 /* 25 * 调整堆使其堆顶为未排序堆中最大关键字 26 */ 27 public static void AdjustHeap(int[] heap,int location,int unSortlength) { 28 int temp; 29 int tempLoc; 30 /* 31 * 确保左右节点存在 32 */ 33 if ((tempLoc = (location + 1) * 2) < unSortlength) { 34 /* 35 * 判断左右节点大小 36 */ 37 if (heap[tempLoc] >= heap[tempLoc - 1]) { 38 /* 39 * 判断父节点与子节点的大小,若父节点小,则与大的子节点换位 40 */ 41 if (heap[location] < heap[tempLoc]) { 42 temp = heap[location]; 43 heap[location] = heap[tempLoc]; 44 heap[tempLoc] = temp; 45 /* 46 *递归法对换位后的子节点及其子节点进行调整 47 */ 48 AdjustHeap(heap,tempLoc,unSortlength); 49 } 50 } else { 51 /* 52 * 左节点大于右节点 53 */ 54 if (heap[location] < heap[tempLoc - 1]) { 55 temp = heap[location]; 56 heap[location] = heap[tempLoc - 1]; 57 heap[tempLoc - 1] = temp; 58 /* 59 * 递归法对换位后的子节点及其子节点进行调整 60 */ 61 AdjustHeap(heap,tempLoc - 1,unSortlength); 62 } 63 } 64 } 65 /* 66 * 确保左节点存在 67 */ 68 else if ((tempLoc = (location + 1) * 2 - 1) < unSortlength) { 69 /* 70 * 与左节点进行比较 71 */ 72 if (heap[location] < heap[tempLoc]) { 73 /* 74 *左子节点大于父节点,将两者进行换位 75 */ 76 temp = heap[location]; 77 heap[location] = heap[tempLoc]; 78 heap[tempLoc] = temp; 79 AdjustHeap(heap,tempLoc,unSortlength); 80 } 81 } 82 }
六、希尔排序
希尔排序的定义:
希尔排序(Shell Sort)是插入排序的一种。是针对直接插入排序算法的改进。该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。
希尔排序的实现:
希尔排序属于插入类排序,是将整个无序列分割成若干小的子序列分别进行插入排序。
排序过程:先取一个正整数d1<n,把所有序号相隔d1的数组元素放一组,组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止。
希尔排序的代码:
1 static void ShellSort(int Index) { 2 int j; 3 int Temp; // 暂存变量 4 int DataLength; // 分割集合的间隔长度 5 int Pointer; // 进行处理的位置 6 DataLength = (int) Index / 2; // 初始集合间隔长度 7 while (DataLength != 0) // 数列仍可进行分割 8 { 9 // 对各个集合进行处理 10 for (j = DataLength; j < Index; j++) { 11 Temp = a[j]; // 暂存Data[j]的值,待交换值时用 12 Pointer = j - DataLength; // 计算进行处理的位置 13 // 进行集合内数值的比较与交换值 14 while (Pointer >= 0 && Pointer <= Index && Temp < a[Pointer]) { 15 a[Pointer + DataLength] = a[Pointer]; 16 // 计算下一个欲进行处理的位置 17 Pointer = Pointer - DataLength; 18 } 19 // 与最后的数值交换 20 a[Pointer + DataLength] = Temp; 21 } 22 DataLength = DataLength / 2; // 计算下次分割的间隔长度 23 } 24 }
六、归并排序
归并排序的定义:
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个有序的子序列,再把有序的子序列合并为整体有序序列。
归并排序的实现:【这里只说二路归并】
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。值得注意的是归并排序是一种稳定的排序方法。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
图示: