由于待排序的记录数量不同,使得排序过程中涉及的存储器不同,可以将排序方法分为两大类:内部排序和外部排序。 内部排序:待排序记录存放在计算机的随机存储器(内存)中进行的排序过程。内部排序的方法很多,但就其全面性能而言,很难提出一种被认为最好的方法。每一种方法都有自己的优缺点,适合在不同的环境下使用(比如记录的初始排列状态等)。按照排序过程中依据的不同原则对内部排序大概可以分为以下几类:插入排序,交换排序,选择排序,归并排序和基数排序。 外部排序:待排序的记录数量很大,内存一次不能容纳全部记录,在排序过程中需要对外存进行访问的排序过程。(内存和外存同时使用) 各种排序算法之间的关系罗列:
算法概念:每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表任然有序。 算法思想:假设待排序的记录存放在数组R[1..n]中。初始时,R[1]自成1个有序区,无序区为R[2..n]。从i=2起直至i=n为止,依次将R[i]插入当前的有序区R[1..i-1]中,生成含n个记录的有序区。 算法示例:
//(直接)插入排序,时间复杂度O(n^2),稳定 public class InsertSort { public static void main(String[] args) { int[] nums = {5,3,2,7,1,4,6}; startSort(nums); for (int i = 0; i < nums.length - 1; i++) { System.out.println(nums[i]); } } public static void startSort(int[] nums) { for (int i = 0; i < nums.length - 1; i++) { int j = i + 1; //temp中存放需要插入的元素 int temp = nums[j]; //temp为待插入的元素,和已经有序列表的最后一个数(最大数)进行比较 //若max>temp,max向后移动,temp再和前面的次最大数比较... //若max<temp,temp直接插入在最后即可 while(i >= 0 && nums[i] > temp) { nums[j] = nums[i]; i --; j --; } nums[j] = temp; } } } 123456789101112131415161718192021222324252627282930 123456789101112131415161718192021222324252627282930算法总结:时间复杂度O(n^2),是稳定的排序方式。
算法概念:也是一种插入排序的算法,但在时间效率上有较大的改进。 算法思想: (a)先将整个待排序记录分割成若干个子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。 (b)先取一个小于n(n为数组长度)的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d2(其中d2小于d1)重复上述的分组和排序,直至所取的增量dt=1(dt<dt-1<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。 算法示例:
//希尔排序,时间复杂度O(nlogn),不稳定 public class ShellSort { public static void main(String[] args) { int[] nums = {5,3,2,7,1,4,6}; startSort(nums); for (int i = 0; i < nums.length - 1; i++) { System.out.println(nums[i]); } } public static void startSort(int[] nums) { // 分成2组 int len = nums.length / 2; while (len >= 1) { for (int i = 0; i < len; i++) { // 使用直接插入排序对分组进行排序 for (int j = i; j < nums.length - len; j += len) { int k = j + len; int temp = nums[k]; while (j >= 0 && nums[j] > temp) { nums[k] = nums[j]; j -= len; k -= len; } nums[k] = temp; } } len /= 2; } } } 1234567891011121314151617181920212223242526272829303132 1234567891011121314151617181920212223242526272829303132算法总结:时间复杂度O(nlogn),不稳定。
算法思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数中再找出最小的数与第二个位置的数交换,如此循环到倒数第二个数,将该数与最后一个数比较,较小的一个放在前一个位置。 算法示例:
//选择排序,时间复杂度O(n^2),不稳定 public class SelectSort { public static void main(String[] args) { int[] nums = { 5, 3, 2, 7, 1, 4, 6 }; startSort(nums); for (int i = 0; i < nums.length - 1; i++) { System.out.println(nums[i]); } } public static void startSort(int[] nums) { // 标志位 int position = 0; for (int i = 0; i < nums.length; i++) { int j = i + 1; position = i; int temp = nums[i]; // 找出后面数中的最小者 for (; j < nums.length; j++) { if (nums[j] < temp) { temp = nums[j]; position = j; } } nums[position] = nums[i]; nums[i] = temp; } } } 123456789101112131415161718192021222324252627282930 123456789101112131415161718192021222324252627282930算法总结:时间复杂度O(n^2),不稳定。
算法思想:堆排序是一种用完全二叉树解决问题的高效算法,合法的最大堆就是每一个节点值都要大于或等于它的孩子节点,在数组中可表示为:(i从0开始)arrays[i]>=arrays[2*i+1] && arrays[i]>=arrays[2*i+2],最小堆的概念和最大堆相反,此处采用最大堆。 堆排序树的构造过程找最大值过程由下图,数组arrays[0….n]为:17,8,45,84,2,94,刚找到最大值后把最大值即94放在数组的最后面arrays[n],然后进入递归把arrays[0…n-1]再进入下面图这个过程,只是把排好序的最大值不放入到这个过程中,就这样把值一个个的冒出来,找到最大值后把这个最大值放到数组的最后面,进入下一个递归。 注意:每次都要扫描整个数组,最先发现不符合最大堆的先调整。 算法示例:
//堆排序,时间复杂度O(nlogn),不稳定 public class HeapSort { public static void main(String[] args) { int[] nums = {5,3,2,7,1,4,6}; int e = nums.length - 1; nums = startSort(nums,e); for (int i = 0; i < nums.length - 1; i++) { System.out.println(nums[i]); } } // 堆排序 public static int[] startSort(int[] nums, int e) { // 堆中的元素多于1个 if (e > 0) { initHeap(nums, e);// 初始化堆 // 在一轮的堆排序中,找到了最大值,将最大值与最后一个元素交换,重新开始下一轮 nums[0] = nums[e] + nums[0]; nums[e] = nums[0] - nums[e]; nums[0] = nums[0] - nums[e]; // 递归开始下一轮 startSort(nums, e - 1); } else { // 堆中不多于1个元素,不需要进行堆排序 return nums; } return nums; } // 初始化堆 public static void initHeap(int[] nums, int e) { int m = (e + 1) / 2; // 父节点的个数 for (int i = 0; i < m; i++) { boolean flag = buildHeap(nums, e, i); // 如果父节点和孩子节点之间有交换,需要重新扫描整个树中的父节点 if (flag) { i = -1; } } } // 建立堆 public static boolean buildHeap(int[] nums, int e, int i) { int l_child = 2 * i + 1;// 左孩子 int r_child = 2 * i + 2;// 右孩子 // 没有右孩子的时候只需要比较父节点和左孩子节点的大小 if (r_child > e) { // 父节点比左孩子节点小,需要交换 if (nums[i] < nums[l_child]) { // 不使用中间变量交换父节点和左孩子节点的值 nums[i] = nums[i] + nums[l_child]; nums[l_child] = nums[i] - nums[l_child]; nums[i] = nums[i] - nums[l_child]; return true; } else { return false; } } // 同时拥有左右两个孩子节点的情况,在三者中找出最大值进行交换 if (nums[i] < nums[l_child]) { // 父节点比左孩子节点小 if (nums[l_child] > nums[r_child]) { // 左孩子节点是三者中最大的,和父节点交换 nums[i] = nums[i] + nums[l_child]; nums[l_child] = nums[i] - nums[l_child]; nums[i] = nums[i] - nums[l_child]; return true; } else { // 右孩子节点是三者中最大的,和父节点交换 nums[i] = nums[i] + nums[r_child]; nums[r_child] = nums[i] - nums[r_child]; nums[i] = nums[i] - nums[r_child]; return true; } } else if (nums[i] < nums[r_child]) { // 父节点比右孩子节点小 // 右孩子节点是三者中最大的,和父节点交换 nums[i] = nums[i] + nums[r_child]; nums[r_child] = nums[i] - nums[r_child]; nums[i] = nums[i] - nums[r_child]; return true; } // 父节点最大,不需要交换 return false; } } 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889算法总结:时间复杂度O(nlogn),不稳定。
算法思想:在要排序的一组数中,将第一个记录的关键字与第二个记录的关键字进行比较,让大的往下沉,小的往上冒,然后再比较第二个和第三个,以此类推。比较完一趟,最大的那个已经放到了最后的位置,这样可以对剩下的n-1个再循环比较。 算法示例:
//冒泡排序,时间复杂度O(n^2),稳定 public class BubbleSort { public static void main(String[] args) { int[] nums = {5,3,2,7,1,4,6}; startSort(nums); for (int i = 0; i < nums.length - 1; i++) { System.out.println(nums[i]); } } public static void startSort(int[] nums) { for (int i = 0; i < nums.length - 1; i++) { for (int j = 0; j < nums.length - 1 - i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; } } } } } 12345678910111213141516171819202122232425 12345678910111213141516171819202122232425算法总结:算法复杂度为O(n^2),稳定的。
算法思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,这里选择第一个元素为基准。一个指向基准元素且整个过程指针不变,一个指向最后一个元素,两者比较,前面的元素大于后面的元素则交换,非基准元素的指针向中心移动一位,继续上述步骤,通过一趟扫描,将待排序列分成两个部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素的位置是排好序后的正确位置,然后再用同样的方法递归地排序两个部分。 快速排序是对冒泡排序的一种改进。 算法示例:
//快速排序,时间复杂度O(nlogn),不稳定 public class QuickSort { public static void main(String[] args) { int[] nums = {5,3,2,7,1,4,6}; int low = 0; int high = nums.length - 1; nums = startSort(nums,low,high); for (int i = 0; i < nums.length - 1; i++) { System.out.println(nums[i]); } } public static int[] startSort(int[] nums,int low,int high) { //递归的终止条件是low >= high if(low < high) { int middle = getMiddle(nums,low,high); //对基准元素前面部分递归排序 startSort(nums,low,middle-1); //对基准元素后面部分递归排序 startSort(nums,middle+1,high); } return nums; } public static int getMiddle(int[] nums, int low,int high) { //选择基准元素 int temp = nums[low]; //两个指针都向中心处移动,直至相等 while (low < high) { //右指针的元素总是比基准元素大时 while (low < high && nums[high] >= temp) { high --; } //比基准元素小的移动到左侧 nums[low] = nums[high]; //左指针的元素总是比基准元素小时 while (low <high && nums[low] <= temp) { low ++; } //比基准元素大的移动到右侧 nums[high] = nums[low]; } nums[low] = temp; return low; } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849算法总结:时间复杂度O(nlogn),不稳定。
算法思想:将两个(或者两个以上)的有序表合并成一个新的有序表,即把待排序序列分成若干个子序列,每个子序列都是有序的,然后再把有序子序列合并为整体有序序列。 算法在排序的过程中会将整个序列分成两个数为一单元的组合,然后设定两个指针,最初位置分别为两个已经排序序列(一开始两个单独的数即为两个已经排序的序列)的起始位置,然后比较两个指针所指向的元素,选择相对小的元素放入到临时空间,并移动指针到下一位置,直到某一指针移动到序列尾,将另一序列剩下的所有元素直接复制到合并序列的尾部。 算法示例:
//归并排序,时间复杂度O(nlogn),稳定 public class MergeSort { public static void main(String[] args) { int[] nums = {5,3,2,7,1,4,6}; int low = 0; int high = nums.length - 1; startSort(nums,low,high); for (int i = 0; i < nums.length - 1; i++) { System.out.println(nums[i]); } } // 归并排序 public static int[] startSort(int[] nums, int low, int high) { // 找出中间的索引 int mid = (low + high) / 2; if (low < high) { // 对左边数组进行递归 startSort(nums, low, mid); // 对右边数组进行递归 startSort(nums, mid + 1, high); // 左右归并 merge(nums, low, mid, high); } return nums; } // 将左右两个有序的子序列进行合并 public static void merge(int[] nums, int low, int mid, int high) { int[] temp = new int[high - low + 1]; int i = low;// 左指针 int j = mid + 1;// 右指针 int k = 0; // 把较小的数先移动到新数组中 while (i <= mid && j <= high) { if (nums[i] < nums[j]) { temp[k++] = nums[i++]; } else { temp[k++] = nums[j++]; } } // 把左边剩余的数移入数组 while (i <= mid) { temp[k++] = nums[i++]; } // 把右边剩余的数移入数组 while (j <= high) { temp[k++] = nums[j++]; } // 把新数组中的数覆盖nums数组 for (int k2 = 0; k2 < temp.length; k2++) { nums[k2 + low] = temp[k2]; } } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758算法总结:算法复杂度O(nlogn),稳定。
算法思想:将所有待比较值(正整数)统一为同样的数位长度,数位较短的数前面补0,然后从最低位开始,进行依次排序。这样从最低位一直到最高位排序完成以后,数列就变成了一个有序的序列。 在众多的排序方法中,基数排序比较特殊,它是一种不需要进行关键字之间比较的排序方法,利用多关键字的划分,逐渐实现有序。 比如:278,109,63,930,589,184,505,269,8,83进行排序的过程如下: (a)第一次分组,每一个元素每一位上的数值都是0~9,所以划分为10组,按照每个元素个位上的数值进行分组: 0组:930 1组: 2组: 3组:63,83 4组:184 5组:505 6组: 7组: 8组:278,8 9组:109,589,269 第一次排序后的结果:930,63,83,184,505,278,8,109,589,269 (b)第二次分组,将第一次分组后的结果按照十位上的数进行分组: 0组:505,8,109 1组: 2组: 3组:930 4组: 5组: 6组:63,269 7组:278 8组:83,184,589 9组: 第二次排序后的结果:505,8,109,930,63,269,278,83,184,589 (c)第三次分组,将第二次分组后的结果按照百位上的数进行分组: 0组:8,63,83 1组:109,184 2组:278,269 3组: 4组: 5组:505,589 6组: 7组: 8组: 9组:930 第三次排序后的结果:8,63,83,109,184,278,269,505,589,930 (d)最高只有百位,没有千位,排序结束,输出结果。 算法示例:
import java.util.ArrayList; import java.util.List; //基数排序,时间复杂度O(nlogrm),稳定 public class RadixSort { public static void main(String[] args) { int[] nums = {5,3,2,7,1,4,6}; nums = startSort(nums); for (int i = 0; i < nums.length - 1; i++) { System.out.println(nums[i]); } } // 基数排序 public static int[] startSort(int[] nums) { int max = nums[0]; // 选出数列中的最大值 for (int i = 1; i < nums.length; i++) { if (nums[i] > max) { max = nums[i]; } } // 计算总共需要比较多少次 // 注意此处小于10的正整数除以10结果为0 int time = 0; while (max > 0) { max /= 10; time++; } // 设定10个list保存数据 List<ArrayList<Integer>> queue = new ArrayList<ArrayList<Integer>>(); for (int j = 0; j < 10; j++) { ArrayList<Integer> dataQueue = new ArrayList<Integer>(); queue.add(dataQueue); } // 开始排序 for (int i = 0; i < time; i++) { for (int j = 0; j < nums.length; j++) { // 取到当前这个数的对应位置上的一位数 int x = nums[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i); ArrayList<Integer> returnQueue = queue.get(x); returnQueue.add(nums[j]); queue.set(x, returnQueue); } int count = 0; for (int k = 0; k < 10; k++) { while (queue.get(k).size() > 0) { ArrayList<Integer> tempQueue = queue.get(k); nums[count] = tempQueue.get(0); tempQueue.remove(0); count++; } } } return nums; } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061算法总结:时间复杂度O(nlogrm),稳定。
总结:没有哪一种排序算法是绝对最优的,要看具体的应用场景。
转载自:http://blog.csdn.net/u012050416/article/details/50681591