十大排序算法之Java实现
选择排序
首先找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。其次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。这种方法我们称之为选择排序。
public class SelectSort { public static int[] selectSort(int[] a) { int n = a.length; for(int i = 0; i < n; i++) {g int min = i; for(int j = i+1; j < n; j++) { if(a[min] > a[j]) min = j; } // 交换 int temp = a[i]; a[i] = a[min]; a[min] = temp; } return a; } }
1、 时间复杂度:O(n2)
2、空间复杂度:O(1)
3、非稳定排序
4、原地排序
插入排序
插入排序的一个简单理解就是我们平时在打牌时,将每一张牌插入到其他已经有序的牌中的适当位置。当我们给无序的数组做排序时,为了插入元素,我们需要先腾出空间,将其余所有元素在插入之前都向右移动一位,这种算法我们称之为插入排序。
public class InsertSort { public static int[] insertSort(int[] arr) { if(arr == null || arr.length < 2) return arr; int n = arr.length; for(int i = 1; i < n; i++) { int temp = arr[i]; int k = i - 1; while(k >= 0 && arr[k] > temp) k--; // 腾出位置插进去,要插的位置是k+1 for(int j = i; j > k; j--) arr[j] = arr[j-1]; // 插进去 arr[k+1] = temp; } return arr; } }
1、 时间复杂度:O(n2)
2、空间复杂度:O(1)
3、稳定排序
4、原地排序
冒泡排序
把第一个元素与第二个元素相比较,如果第一个比第二个大,则交换他们的位置。接着继续比较第二个与第三个元素,如果第二个比第三个大,则交换他们的位置...
非优化版本
public class BubbleSort { public static int[] bubbleSort(int[] arr) { if(arr == null || arr.length < 2) { return arr; } int n = arr.length; for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { int t = arr[j]; arr[j] = arr[j+1]; arr[j+1] = t; } } return arr; } }
1、 时间复杂度:O(n2)
2、空间复杂度:O(1)
3、稳定排序
4、原地排序
优化版本
假如从开始的第一对到结尾的最后一对,相邻的元素之间都没有发生交换的操作,这意味着右边的元素总是大于等于左边的元素,此时的数组已经是有序的了,我们无需再对剩余的元素重复比较下去了。基于这点我们可以对上述代码做如下优化。
public class BubbleSort { public static int[] bubbleSort(int[] arr) { if(arr == null || arr.length < 2) { return arr; } int n = arr.length; for(int i = 0; i < n; i++) { boolean flag = true; for(int j = 0; j < n; j++) { if(arr[j+1] < arr[j]) { flag = false; int t = arr[j+1]; arr[j+1] = t; } } // 遍历一遍下来看是否发生过位置交换 if(false) break; } return arr; } }
希尔排序
1 public class ShellSort { 2 public static int[] shellSort(int arr[]) { 3 if(arr == null || arr.length < 2) return arr; 4 int n = arr.length; 5 // 对每组间隔为h的分组进行排序,刚开始h = n / 2; 6 for(int h = n/2; h > 0; h /= 2) { 7 int i = h; i < h; i++) { 8 // 将arr[i]插入到所在分组的正确位置上 9 insert(arr, h, i); 10 } 11 } 12 return arr; 13 } 14 15 /** 16 * 将arr[i]插入到所在分组的正确位置上 17 * arr[i]所在分组为 ... arr[i-2*h], arr[i-h], arr[i+h] ... 18 */ 19 private static void insertI(int[] arr, int h, int i) { 20 int temp = arr[i]; 21 int k; 22 for(k = i-h; k > 0 && temp < arr[k]; k -= h) { 23 arr[k+h] = arr[k]; 24 } 25 arr[k+h] = temp; 26 } 27 }
1、 时间复杂度:O(n2)
2、空间复杂度:O(1)
3、非稳定排序
4、原地排序
归并排序
递归式归并排序
1 public class MergeSort { 2 public static int[] mergeSort(int[] arr, int left, int right) { 3 // 如果left==right,表示数组只有一个元素,则不用递归排序 4 if(left < right) { 5 // 把大的数组分隔成两个数组 6 int mid = (left + right) / 2; 7 // 对左半部分进行排序 8 arr = mergeSort(arr, left, mid); 9 // 对右半部分进行排序 10 arr = mergeSort(arr, mid+1, right); 11 // 进行合并 12 merge(arr, left, mid, right); 13 } 14 return arr; 15 } 16 17 // 合并函数,把两个有序的数组合并起来 18 // arr[left..mif]表示一个数组,arr[mid+1..right]表示一个数组 19 private static void merge(int[] arr, int left, int mid, int right) { 20 // 先用一个临时数组把他们合并汇总起来 21 int[] a = new int[right - left + 1]; 22 int i = left; 23 int j = mid + 1; 24 int k = 0; 25 while(i <= mid && j <= right) { 26 if(arr[i] < arr[j] { 27 a[k++] = arr[i++]; 28 } else { 29 a[k++] = arr[j++]; 30 } 31 } 32 while(i <= mid) a[k++] = arr[i++]; 33 while(j <= right) a[k++] = arr[j++]; 34 // 把临时数组复制到原数组 35 for(i = 0; i < k; i++) { 36 arr[left++] = a[i]; 37 } 38 } 39 }
1、 时间复杂度:O(n2)
2、空间复杂度:O(n)
3、稳定排序
4、原地排序
非递归式归并排序
1 public class MergeSort { 2 // 非递归式的归并排序 3 public static int[] mergeSort(int[] arr) { 4 // 子数组的大小分别是1, 2, 4, 8. ... 5 // 刚开始合并的数组大小是1,接着是2,接着4,... 6 for(int i = 1; i < n; i += i) { 7 // 进行数组进行划分 8 int left = 0; 9 int mid = left + i - 1; 10 int right = mid + i; 11 12 // 进行合并,对数组大小为i的数组进行两两合并 13 while(right < n) { 14 // 合并函数和递归式的合并函数一样 15 merge(arr, left, mid, right); 16 left = right + 1; 17 mid = left + i - 1; 18 right = mid + 1; 19 } 20 21 // 还有一些被遗漏的数组的合并,因为不可能每个数组的大小都刚好为1 22 if(left < n && mid < n) { 23 merge(arr, left, mid, n - 1); 24 } 25 } 26 return arr; 27 } 28 }
快速排序
我们先从数组中选择一个元素作为中轴元素,然后把数组中所有小于中轴元素的元素放在其左边,所有大于或等于中轴元素的元素放在其右边,显然,此时中轴元素所处的位置是有序。也就是说,我们无需再移动中轴元素的位置。
然后从中轴元素那里开始把大的数组切割成两个小的数组(两个数组都不包含中轴元素),接着我们通过递归的方式,让中轴元素左边的数组和右边的数组也重复同样的操作,知道数组的大小为1,此时每个元素都处于有序的位置。
1 public class QuickSort { 2 public static int[] quickSort(int[] arr, int left, int right) { 3 if(left < right) { 4 // 获取中轴元素所处的位置 5 int mid = partition(arr, left, right); 6 7 // 进行分割 8 arr = quickSort(arr, left, mid - 1); 9 arr = quickSort(arr, mid + 1, right); 10 } 11 return arr; 12 } 13 14 private static int partition(int[] arr, int left, int right) { 15 // 选取中轴元素 16 int pivot = arr[left]; 17 int i = left + 1; 18 int j = right; 19 while(true) { 20 // 向右找到第一个小于等于pivot的元素位置 21 while(i <= j && arr[i] <= pivot) i++; 22 // 向左找到第一个大于的等于pivot的元素位置 23 while(i <= j && arr[j] >= pivot) j--; 24 if(i >= j) 25 break; 26 // 交换两个元素的位置,使得左边的元素不大于pivot,右边的不小于pivot 27 int temp = arr[i]; 28 arr[i] = arr[j]; 29 arr[j] = temp; 30 } 31 arr[left] = arr[j]; 32 // 使中轴元素处于有序的位置 33 arr[j] = pivot; 34 return j; 35 } 36 }
1、 时间复杂度:O(nlogn)
2、空间复杂度:O(logn)
3、非稳定排序
4、原地排序
堆排序
堆的特点就是堆顶的元素是一个最值,大顶堆的堆顶是最大值,小堆顶则是最小值。
堆排序就是把堆顶的元素与最后一个元素交换,交换之后就破坏了堆的特性。我们再把堆中剩余的元素再次构成一个大顶堆,然后再把堆顶元素与最后第二个元素交换....如此往复下去,等到剩余的元素只有一个的时候,此时的数组就是有序 的了。
1 public class Head { 2 // 堆排序 3 public static int[] headSort(int[] arr) { 4 5 int n = arr.length; 6 // 构建⼤大顶堆 7 for (int i = (n - 2) / 2; i >= 0; i--) { 8 downAdjust(arr, i, n - 1); 9 } 10 11 // 进⾏行行堆排序 12 for (int i = n - 1; i >= 1; i--) { 13 // 把堆顶元素与最后⼀一个元素交换 14 int temp = arr[i]; 15 arr[i] = arr[0]; 16 arr[0] = temp; 17 // 把打乱的堆进⾏行行调整,恢复堆的特性 18 downAdjust(arr, 0, i - 1); 19 } 20 21 return arr; 22 } 23 24 // 下沉操作 25 public static void downAdjust(int[] arr, int parent, int n) { 26 // 临时保存要下沉的元素 27 int temp = arr[parent]; 28 // 定位左孩⼦子节点的位置 29 int child = 2 * parent + 1; 30 // 开始下沉 31 while (child <= n) { 32 // 如果右孩⼦子节点⽐比左孩⼦子⼤大,则定位到右孩⼦子 33 if(child + 1 <= n && arr[child] < arr[child + 1]) 34 child++; 35 // 如果孩⼦子节点⼩小于或等于⽗父节点,则下沉结束 36 if (arr[child] <= temp ) break; 37 // ⽗父节点进⾏行行下沉 38 arr[parent] = arr[child]; 39 parent = child; 40 child = 2 * parent + 1; 41 } 42 arr[parent] = temp; 43 } 44 }
1、 时间复杂度:O(n2)
2、空间复杂度:O(1)
3、非稳定排序
4、原地排序
基数排序
非优化版本
优化版本
桶排序
1 public class BucketSort { 2 public static int[] BucketSort(int[] arr) { 3 if(arr == null || arr.length < 2) return arr; 4 int n = arr.length; 5 int max = arr[0]; 6 int min = arr[0]; 7 8 // 寻找数组的最⼤大值与最⼩小值 9 for (int i = 1; i < n; i++) { 10 if(min > arr[i]) 11 min = arr[i]; 12 if(max < arr[i]) 13 max = arr[i]; 14 } 15 16 // 和优化版本的计数排序⼀一样,弄弄⼀一个⼤大⼩小为 min 的偏移值 17 int d = max - min; 18 // 创建 d / 5 + 1 个桶,第 i 桶存放 5*i ~ 5*i+5-1范围的数 19 int bucketNum = d / 5 + 1; 20 ArrayList<LinkedList<Integer>> bucketList = new ArrayList<>(bucketNum); 21 22 // 初始化桶 23 for (int i = 0; i < bucketNum; i++) { 24 bucketList.add(new LinkedList<Integer>()); 25 } 26 27 // 遍历原数组,将每个元素放⼊入桶中 28 for (int i = 0; i < n; i++) { 29 bucketList.get((arr[i]-min)/d).add(arr[i] - min); 30 } 31 32 // 对桶内的元素进⾏行行排序,我这⾥里里采⽤用系统⾃自带的排序⼯工具 33 for (int i = 0; i < bucketNum; i++) { 34 Collections.sort(bucketList.get(i)); 35 } 36 37 // 把每个桶排序好的数据进⾏行行合并汇总放回原数组 38 int k = 0; 39 for (int i = 0; i < bucketNum; i++) { 40 for (Integer t : bucketList.get(i)) { 41 arr[k++] = t + min; 42 } 43 } 44 return arr; 45 } 46 }
性质:
1、时间复杂度:O(n+k)
2、空间复杂度:O(n+k)
3、稳定排序
4、⾮非原地排序
注:k 表示桶的个数