虽然网上有很多排序算法的代码了,但是我还是想根据所学将自己的想法展示出来。
1.冒泡排序
class Solution {
public static void bubbleSort(int[] nums) {
for (int i = 0; i < nums.length; 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;
}
}
}
}
public static void main(String[] args) {
int[] nums = {3,4,8,12,4,7,56,34};
bubbleSort(nums);
for (int num : nums) {
System.out.println(num);
}
}
}
这是稳定的排序算法,时间复杂度为O(n^2),因为没有使用额外空间,所以空间复杂度为O(1)。
2.选择排序
//遍历数组,找到最大(最小)的元素,将它与数组中第一个元素进行交换,然后继续找,再和第二个元素进行交换...
class Solution {
public static void selectionSort(int[] nums) {
//遍历到的最小元素的下标
int minIndex;
for (int i = 0; i < nums.length; i++) {
minIndex = i;
for (int j = i + 1; j < nums.length; j++) {
if (nums[minIndex] > nums[j]) {
minIndex = j;
}
}
int temp = nums[i];
nums[i] = nums[minIndex];
nums[minIndex] = temp;
}
}
public static void main(String[] args) {
int[] nums = {3, 4, 8, 12, 4, 7, 56, 34};
selectionSort(nums);
for (int num : nums) {
System.out.println(num);
}
}
}
这是不稳定的排序算法,时间复杂度为O(n^2),因为没有使用额外空间,所以空间复杂度为O(1)。
3.插入排序
class Solution {
public static void insertSort(int[] nums) {
int len = nums.length;
//要插入的数
int insertNum;
//默认第一个元素是有序的,所以i从1开始
for (int i = 1; i < len; i++) {
insertNum = nums[i];
int j = i - 1;
//从后向前遍历,将大于要插入的数的元素后移
while (j >= 0 && nums[j] > insertNum) {
nums[j + 1] = nums[j];
j--;
}
nums[j + 1] = insertNum;
}
}
public static void main(String[] args) {
int[] nums = {3, 4, 8, 12, 4, 7, 56, 34};
insertSort(nums);
for (int num : nums) {
System.out.println(num);
}
}
}
这是稳定的排序算法,时间复杂度为O(n^2),因为没有使用额外空间,所以空间复杂度为O(1)。
4.快速排序
实现:
快速排序是用递归实现的,首先初始化一个空数组,在原数组中选取一个基准数,然后遍历原数组中的元素,将比基准数小的元素放入空数组的前半部分,比基准数大的元素放入空数组的后半部分,最后将基准数填入空白位置,这样就将原数组分割成两部分。分别对左右部分的数组进行上述操作,最终可以得到一个有序的数组。
改进:
上述实现需要初始化一个空数组,空间复杂度较高,其实并不用初始化一个空数组,只需要使用两个指针,分别为分区指示器和遍历指示器即可。
实现是这样的:
-
在数组中选取一个元素作为基准数,将其与数组尾元素进行交换,分区指示器初始指向的下标为数组开始下标-1,遍历指示器初始指向的下标为数组开始下标。
-
遍历指示器开始遍历数组,如果当前元素比小于等于基准数,则分区指示器右移一位,移动后如果当前元素下标大于分区指示器的下标,那么分区指示器指向的元素与当前元素进行交换;如果当前元素大于基准数,则分区指示器不变。
-
遍历结束之后,就会发现基准数将原来的数组分割成左右两部分,左半部分的元素比基准数小,右半部分的元素比基准数大,左右两侧的数组重复上述操作,最终能够得到一个有序数组。
class Solution {
private static void sortArray(int[] nums) {
quickSort(nums, 0, nums.length - 1);
}
public static void quickSort(int[] array, int start, int end) {
if (array.length < 1 || start < 0 || end >= array.length || start > end)
return;
//数据被分割成独立的两部分时,从哪分区的指示器
int zoneIndex = partition(array, start, end);
if (zoneIndex > start)
quickSort(array, start, zoneIndex - 1);
if (zoneIndex < end)
quickSort(array, zoneIndex + 1, end);
}
private static int partition(int[] array, int start, int end) {
//只有一个元素时,无需再分区
if (start == end) return start;
//随机选取一个基准数
int pivot = (int) (start + Math.random() * (end - start + 1));
//zoneIndex是分区指示器,初始值为分区头元素下标减一
int zoneIndex = start - 1;
//将基准数和分区尾元素交换位置
swap(array, pivot, end);
for (int i = start; i <= end; i++) {
//当前元素小于基准数
if (array[i] <= array[end]) {
//首先分区指示器累加
zoneIndex++;
//当前元素在分区指示器的右边时,交换当前元素和分区指示器元素
if (i > zoneIndex)
swap(array, i, zoneIndex);
}
}
return zoneIndex;
}
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
int[] nums = {23, 53454, 6456, 4233, 232, 45, 2334};
sortArray(nums);
for (int num : nums) {
System.out.println(num);
}
}
}
这是不稳定的排序算法,时间复杂度为O(nlogn ),因为使用到了递归,时间复杂度为O(logn)。
5.希尔排序
又称为缩小增量排序。
class Solution {
private static void shellSort(int[] nums) {
if (nums == null) return;
int len = nums.length;
/*按增量分组后,每个分组中,currentValue代表当前待排序数据,该元素之前的组内数据均已被排序过*/
/*gap指用来分组的增量,会依次递减*/
int currentValue, gap = len / 2;
while (gap > 0) {
for (int i = gap; i < len; i++) {
currentValue = nums[i];
/*组内已被排序数据的索引*/
int preIndex = i - gap;
/*在组内已被排序过的数据中倒序寻找合适的位置,如果当前待排序数据比比较的元素要小,
则将比较的元素在组内后移一位*/
while (preIndex >= 0 && nums[preIndex] > currentValue) {
nums[preIndex + gap] = nums[preIndex];
preIndex -= gap;
}
/*while循环结束时,说明已经找到了当前待排序数据的合适位置,插入*/
nums[preIndex + gap] = currentValue;
}
gap /= 2;
}
}
public static void main(String[] args) {
int[] nums = {23, 53454, 6456, 4233, 232, 45, 2334};
shellSort(nums);
for (int num : nums) {
System.out.println(num);
}
}
}
这是不稳定的排序算法,时间复杂度为O(nlogn),空间复杂度为O(1)。
6.堆排序
这里以升序为例:
利用完全二叉树的性质排序一个数组,对于任意一个根节点 i ,2*i+1为左子树节点,2 *(i+1)为右子树节点。通过不断调整,使之成为一个最大堆,将数组中第一个元素和尾元素进行交换,同时数组长度-1(剥离排序好的元素),接着再次递归调整堆,使之再次成为一个最大堆,再进行交换...,直到全部排序好为止。
class Solution {
private static int len;
private static void sortArray(int[] nums) {
len = nums.length;
if (len < 1) return;//这里也是保证程序的健壮性
/*1.构建一个最大堆*/
buildMaxHeap(nums);
/*2.循环将堆首位(最大值)与未排序数据末位交换,然后重新调整为最大堆*/
while (len > 0) {
swap(nums, 0, len - 1);
len--;
adjustHeap(nums, 0);
}
}
/**
* 建立最大堆
*/
private static void buildMaxHeap(int[] array) {
/*从最后一个非叶子结点开始向上构造最大堆*/
for (int i = (len / 2 - 1); i >= 0; i--) {
adjustHeap(array, i);
}
}
/**
* 调整使之成为最大堆
*/
private static void adjustHeap(int[] array, int i) {
int maxIndex = i;
int left = 2 * i + 1;
int right = 2 * (i + 1);
/*如果有左子树,且左子树大于父节点,则将最大指针指向左子树*/
if (left < len && array[left] > array[maxIndex])
maxIndex = left;
/*如果有右子树,且右子树大于父节点且大于左子树,则将最大指针指向右子树*/
if (right < len && array[right] > array[maxIndex] && array[right] > array[left])
maxIndex = right;
if (maxIndex != i) {
swap(array, maxIndex, i);
adjustHeap(array, maxIndex);
}
}
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
int[] nums = {23, 53454, 6456, 4233, 232, 45, 2334};
sortArray(nums);
for (int num : nums) {
System.out.println(num);
}
}
}
这是不稳定的排序算法,时间复杂度为O(nlogn),空间复杂度为O(1)。
7.归并排序
使用的是分治法的思想,先将一个数组一分为二,然后继续对拆开的数组进行拆分,直到每个组只有一个元素为止;再把相邻的数组进行合并,合并的过程中进行排序,合并完成也就排好序了。
class Solution {
public static void mergeSort(int[] nums) {
sortSelection(nums, 0, nums.length - 1);
}
private static void sortSelection(int[] nums, int start, int end) {
if (start == end) //只有一个元素时,停止再分
return;
int mid = (start + end) / 2;
sortSelection(nums, start, mid);
sortSelection(nums, mid + 1, end);
merge(nums, start, mid + 1, end);
}
private static void merge(int[] nums, int start, int start1, int end) {
int len = start1 - start;//左边数组的长度
int[] temp = new int[len];
System.arraycopy(nums, start, temp, 0, len);
int p1 = 0, p2 = start1;
for (int i = start; i <= end; i++) {
if (temp[p1] < nums[p2]) {
nums[i] = temp[p1];
p1++;
if (p1 == len) //左半部分的数组已经排序完了
break;
} else {
nums[i] = nums[p2];
p2++;
if (p2 > end) {
while (p1 < len) { // 这里应该先++i,因为索引为i的位置已经被赋值了,这是是把左边部分数组的内容直接赋值到nums中
nums[++i] = temp[p1++];
}
}
}
}
}
public static void main(String[] args) {
int[] nums = new int[50];
Random random = new Random();
for (int i = 0; i < 50; i++) {
nums[i] = random.nextInt(100);
}
mergeSort(nums);
for (int num : nums) {
System.out.print(num + "\t");
}
}
}
这是稳定的排序算法,时间复杂度为O(nlogn),因为使用了额外数组,空间复杂度为O(n)。
总结:
冒泡排序、插入排序、归并排序是稳定的排序算法。
选择排序、希尔排序、快速排序和堆排序是不稳定的排序算法。