自己学习排序算法的练习总结
https://github.com/pengyuntao/Sort_Algorithms_Java
判断排序算法是否稳定:就是判断原本相等的两个数前后相对位置有没有变化,没有变化就是稳定,变化了就是不稳定。
冒泡排序
需要两两比较,大的数字逐渐移动到后边,很像水中的气泡冒出来,成为冒泡排序。
时间复杂度为O(n^2),稳定的排序算法
public class BubbleSort {
//排序算法稳定 排序后与排序前两个相等的元素前后相对位置没有变化
private void swap(int[] array, int key1, int key2) {
int temp = array[key1];
array[key1] = array[key2];
array[key2] = temp;
}
/**
* 相邻两两比较,大数逐渐冒泡到最右边,内部循环的长度越来越小
*
* @param array
*/
private void bubbleSort(int[] array) {
for (int i = 0; i < array.length; i++) {
boolean isSwap = false;
for (int j = array.length - 2; j >= i; j--) {
if (array[j] > array[j + 1]) {
swap(array, j, j + 1);
isSwap = true;
}
}
if (!isSwap) {
break;
}
}
}
@Test
public void testCase1() {
int[] arr = {85, 24, 11, 17, 63, 45, 17, 31, 96, 50};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
}
快速排序
主要是分治思想,对每个子数组进行快排,直到全排完为止。partition方法作用是把数据分成两部分,小的在左边,大的在右边。有两种写法:一种是两个指针从两端向中间扫描,另一种是两个指针同时从左向右扫描。最终结果是把比枢轴位置的数大的调整到数组右边,比枢轴位置的数小的数调整到数组左边。
时间复杂度为O(nlogn)是不稳定的排序
public class QuickSort {
private void swap(int[] array, int key1, int key2) {
int temp = array[key1];
array[key1] = array[key2];
array[key2] = temp;
}
private int partition1(int[] array, int low, int high) {
// 利用Random随机获取low到high之间的一个值作枢轴索引,并将其与array[high]进行交换
int pivot = new Random().nextInt(high - low) + low;
swap(array, pivot, high);
// 单向扫描:right向右遍历array,当遇到小于pivot的元素,则与left当前指向的元素进行交换,
// 否则直接跳过,一直到达array的最右边right为主动遍历,left为被动遍历
int left = low, right = low;
while (right < high) {
if (array[right] < array[high]) {
// 如果array[r]是array中最大的元素,则right遇到的所有元素都要与left指向的元素进行交换
// 如果left与right相等,则交换是不必要的
if (left != right) {
swap(array, left, right);
}
left++;
right++;
} else {
// 如果array[r]是array中最小的元素,则left会一直停留在l处
right++;
}
}
// 最终需要将pivot元素换回其排序最终位置,也就是left当前的位置
swap(array, left, high);
return left;
}
private int partition2(int[] array, int low, int high) {
int key = array[low];
while (low < high) {
while (low < high && array[high] >= key) {
high--;
}
swap(array, low, high);
while (low < high && array[low] <= key) {
low++;
}
swap(array, low, high);
}
return low;
}
private void quickSort1(int[] array, int low, int high) {
if (low < high) {
int pivot = partition1(array, low, high);
quickSort1(array, low, pivot - 1);
quickSort1(array, pivot + 1, high);
}
}
private void quickSort2(int[] array, int low, int high) {
if (low < high) {
int pivot = partition2(array, low, high);
quickSort2(array, low, pivot - 1);
quickSort2(array, pivot + 1, high);
}
}
@Test
public void testCase1() {
int[] arr = {85, 24, 11, 17, 63, 45, 17, 31, 96, 50};
quickSort1(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
@Test
public void testCase2() {
int[] arr = {85, 24, 11, 17, 63, 45, 17, 31, 96, 50};
quickSort2(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
}
堆排序
从小到大排列,使用大顶堆。依次取出大顶堆的顶部元素,对剩下的元素调整堆。重复上述过程。
堆排序时间复杂度为O(nlogn),是不稳定的排序算法
public class HeapSort {
private void swap(int[] array, int key1, int key2) {
int temp = array[key1];
array[key1] = array[key2];
array[key2] = temp;
}
//i为根节点 2*i+1为左孩子 2*i+2为右孩子 length/2-1为最后一个非叶子节点
/**
* 堆排序
*
* @param array
*/
private void heapSort(int[] array) {
for (int i = array.length / 2 - 1; i >= 0; i--) {
adjustHeap(array, i, array.length - 1);
}
for (int i = array.length - 1; i > 0; i--) {
swap(array, 0, i);
adjustHeap(array, 0, i - 1);
}
}
/**
* 调整堆
*
* @param array 数组
* @param start 第一位索引
* @param end 最后一位索引
*/
private void adjustHeap(int[] array, int start, int end) {
int root = array[start];
int r = start;
for (int i = start * 2 + 1; i < end; i = i * 2 + 1) {
if (i < end && array[i] < array[i + 1]) {
//选出两个孩子中大的一个
i++;
}
//因为是大顶堆,如果root比孩子大了那么就不用交换了
if (root > array[i]) {
break;
}
//把大的孩子赋值给局部的根元素
array[r] = array[i];
r = i;
}
array[r] = root;//插入到正确的位置
}
@Test
public void testCase1() {
int[] arr = {85, 24, 11, 17, 63, 45, 17, 31, 96, 50};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
}
插入排序
插入排序的思想类似于摸扑克牌,把摸的牌放入对应位置。默认第一个有序,依次把后边无序的向前边有序的元素中插入,使前边有序的序列向后扩展,直到所有的有序为止。
插入排序时间复杂度为O(n^2),是稳定的排序算法。
public class InsertSort {
public void directInsertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int j = i;
int temp = array[j];
while (j > 0 && temp < array[j - 1]) {
array[j] = array[j - 1];
j--;
}
array[j] = temp;
}
}
@Test
public void testCase1() {
int[] arr = {85, 24, 11, 17, 63, 45, 17, 31, 96, 50};
directInsertSort(arr);
System.out.println(Arrays.toString(arr));
}
}
希尔排序
希尔排序的思想是让数组基本逐渐有序,就是按照一定的步长对元素跳着进行插入排序,然后不断的缩小步长直到1为止。例如初始步长为2,对索引为1,3,5位置的元素进行插入排序,然后缩小步长为1,就是对1,2,3,4,5位置元素排序。由于数组逐渐基本有序,所以每次插入的次数会减少,从而提高了效率。注意每次步长序列的选择目前还是个难题,自己适情况而定,但是最后一次排序步长必须为1.
时间复杂度O(nlogn)~O(n^2),不稳定的排序算法。
public class ShellSort {
public void shellSort(int[] array) {
int inc = array.length;
do {
inc = inc / 3 + 1;//增量,最后一趟排序必须为1
for (int i = inc; i < array.length; i++) {//下边代码为对增量序列进行插入排序
int j = i;
int temp = array[j];
while (j >= inc && array[j - inc] > temp) {
array[j] = array[j - inc];
j = j - inc;
}
array[j] = temp;
}
} while (inc > 1);
}
@Test
public void testCase1() {
int[] arr = {0, 50, 10, 20, 30, 70, 40, 80, 60};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
}
选择排序
选择排序就是内部循环先找到最小的,然后做一次交换,减少了交换次数。
时间复杂度为O(n^2)
public class SelectionSort {
private void swap(int[] array, int key1, int key2) {
int temp = array[key1];
array[key1] = array[key2];
array[key2] = temp;
}
private void simpleSelectionSort(int[] array) {
for (int i = 0; i < array.length; i++) {
int min = i;
for (int j = i; j < array.length; j++) {
if (array[j] < array[min]) {
min = j;
}
}
if (min != i) {
swap(array, min, i);
}
}
}
@Test
public void testCase1() {
int[] arr = {85, 24, 11, 17, 63, 45, 17, 31, 96, 50};
simpleSelectionSort(arr);
System.out.println(Arrays.toString(arr));
}
}
归并排序
归并其实就是先递归分成多部分,直到分成两两比较排序,然后在一点点合并排序。分治思想。
时间复杂度是O(nlogn),稳定的排序算法,缺点需要O(n)的辅助空间。
public class MergeSort {
public void mergeSort(int[] array) {
if (array == null) {
return;
}
mergeSort(array, new int[array.length], 0, array.length - 1);
}
private void mergeSort(int[] array, int[] temp, int start, int end) {
if (start < end) {
int pivot = (start + end) / 2;
mergeSort(array, temp, start, pivot);
mergeSort(array, temp, pivot + 1, end);
merge(array, temp, start, pivot, end);
}
}
private void merge(int[] array, int[] temp, int start, int pivot, int end) {
int i = start, j = pivot + 1, k = start;
while (i <= pivot && j <= end) {
if (array[i] <= array[j]) {
temp[k++] = array[i++];
} else {
temp[k++] = array[j++];
}
}
while (j <= end) {
temp[k++] = array[j++];
}
while (i <= pivot) {
temp[k++] = array[i++];
}
for (int n = start; n <= end; n++) {
array[n] = temp[n];
}
}
@Test
public void testCase1() {
int[] arr = {50, 10, 20, 30, 70, 40, 80, 60};
mergeSort(arr);
System.out.println(Arrays.toString(arr));
}
@Test
public void testCase2() {
int[] arr = {85, 24, 11, 17, 63, 45, 17, 31, 96, 50};
mergeSort(arr);
System.out.println(Arrays.toString(arr));
}
}
桶排序
桶排序是直接利用一个函数把当前元素映射到指定的桶中,有种哈希的感觉。
基数排序
分别对数字的每一位进行排序
计数排序
准备带排列范围内的数量的桶,并按照范围内的数字编号,初始化为0,遍历待排序的数组,遇到一个数字,对应的桶中数字加1,然后按桶顺序及其内部统计的次数来输出排序数字。