package algorithm;
/*
* 所有排序均按照从小到大的顺序
*/
public class Sort {
public static void main(String[] args) {
int[] a = new int[]{0, 8, 7, 6, 1, 10, 11, 11, 9, 2, 5, 4, 3, 11};
// insertionSort(a,0,9);
// quickSort(a, 0, a.length - 1);
// bubbleSort_1(a);
// mergeSort(a);
//heapSort(a);
shellSort(a);
for (int i : a)
System.out.print(i + " ");
}
/*
* 堆排序:O(NlogN)-不稳定
*
* 堆排序是一个平均时间复杂度为O(NlogN)的排序算法,它的思想是以二叉树为基本的数据结构。将数组元素放入一个二叉树中进行调整。
* 这个二叉树满足的条件是任一父节点元素都大于任一子节点元素(大顶堆),或者反过来(小顶堆)。然后建立好初始堆后。每次将堆顶元素和和最后的元素交换,
* 之后堆的元素个数减一遍历进行。直到堆元素只剩一个。
*
* 数据存放在数组int [] arr
*/
/**
* 可以看到建立初始堆和在进行真正堆排序时的循环条件是不一样的, 建立初始堆时 需要把每一个父子节点都进行堆的调整得到一个堆
* 当建立好初始堆之后,就能利用一次堆调整来调整好堆。而无序数组第一次建立初始堆时则不行。这是因为堆的特点是任意
* 父子节点都大于等于(如果为小根堆小于等于)它的两个孩子节点 所以在调整堆函数时只需要比较父子节点和两个孩子节点的大小关系
* 就能把三者的大小顺序调整正确,然后再把子节点向下进行比较 即可,而无序数组不具备堆的特点需要多次循环把每一个父子节点都调整正确后才行
*
* @param A
* @return
*/
public static int[] heapSort(int[] A) {
for (int i = A.length - 1; i >= 0; i--) {// 自底向上循环建立初始堆:最大堆
HeapAdjust(A, i, A.length);
}
for (int i = A.length - 1; i > 0; i--) {// 循环n-1次完成堆排序
// 每次把堆顶元素和最后一个元素替换,然后将剩下的元素进行堆排序
A[0] = A[i] ^ A[0];
A[i] = A[i] ^ A[0];
A[0] = A[i] ^ A[0];
HeapAdjust(A, 0, i);
}
return A;
}
// 上移最大值:使位置parent~length之间的所有父节点的值均大于子节点
public static void HeapAdjust(int[] A, int parent, int length) {
int temp = A[parent];// 保存当前父节点
int child = parent * 2 + 1;// 先取左孩子
while (child < length) {
if (child + 1 < length && A[child] < A[child + 1]) {// 如果右孩子存在并且大于左孩子
// 则选取右孩子
child++;
}
if (temp >= A[child]) {// 如果保存的父节点比孩子大 则循环结束 说明 父节点最大
break;
}
A[parent] = A[child];// 让父节点等于孩子节点
parent = child;
child = child * 2 + 1;// 选取孩子节点的左节点继续向下筛选
}
A[parent] = temp;// 把最后的孩子节点赋值为最初的父节点 相当于节点交换
}
/*
* 快速排序:O(NlogN)-不稳定
*
* 快速排序是一个平均时间复杂度为O(NlogN)的排序算法,它的算法思想是,在一个长度为N的数组上,首先在[0...N-1]
* 的范围内随机选取一个位置作为基准值,然后把这个位置的数和最后一个数交换。之后设置一个记录下标j,j表示[0...j]的范围内的数都比基准值小。
* 然后遍历整个数组和最后一位的基准值作比较。发现小了,则交换当前位置的数和j位置的数。然后整个数组变成以基准值为中间值的两个部分,
* 之后对生成的两个部分递归调用上述方法即可。
*
* 快速排序 当right-left<CUTOFF时,采用插入排序
*/
public static void quickSort(int[] array, int left, int right) {
final int CUTOFF = 3;
if (left + CUTOFF <= right) {
int pivot = median3(array, left, right);
int i = left + 1, j = right - 2;
for (; ; ) {
while (i <= right - 2 && array[i] < pivot)
i++;
while (j >= left + 1 && array[j] > pivot)
j--;
if (i < j)
swap(array, i++, j--);
else
break;
}
swap(array, i, right - 1);
quickSort(array, left, i - 1);
quickSort(array, i + 1, right);
} else
insertionSort(array, left, right);
}
// 插入排序
public static void insertionSort(int[] a, int left, int right) {
if (left == right)
return;
int tmp = 0, j = 0;
for (int i = left; i <= right; i++) {
tmp = a[i];
for (j = i; j > left && a[j - 1] > tmp; j--) {
a[j] = a[j - 1];
}
a[j] = tmp;
}
}
// 枢纽元pivot的选取:三数中值分割法,使用左中右三个元素的中值作为枢纽元,并将pivot存放在right-1的位置上
// 因此要求right - left >= CUTOFF
public static int median3(int[] array, int left, int right) {
// return (a>b)?((b>c)?b:((a>c)?c:a)):((a>c)?a:((b>c)?c:b));
int center = (left + right) >> 1;
if (array[left] > array[center]) // 使得array[left]<=array[center]
swap(array, left, center);
if (array[center] > array[right]) // 使得array[center]<=array[right]同时,array[left]<array[right]
swap(array, center, right);
if (array[center] < array[left])
swap(array, center, left);
swap(array, center, right - 1);
return array[right - 1];
}
public static void swap(int[] array, int a, int b) {
if (array[a] == array[b])
return;
array[a] = array[b] ^ array[a];
array[b] = array[b] ^ array[a];
array[a] = array[b] ^ array[a];
}
/*
* 冒泡排序:O(N^2)-稳定
*
* 冒泡排序是一个平均时间复杂度为O(n^2)的排序算法,它的算法思想是假设有一个数组长度为N,然后在[0...N-1]的范围内,假设i=0。
* 先把第i个数和它后面的数i+1比较,如果大则交换,否则不变。然后继续向后遍历,直到将最大的数交换至数组末尾,然后下一次在[0..N-2]范围内从i
* =0开始。直到范围缩小到[0..1]结束
*
*/
public static int[] bubbleSort(int[] arr) {
int n = arr.length;
int i = 0, j = 0;
for (i = 1; i < n; i++) {
for (j = 0; j < n - i; j++) {
if (arr[j] > arr[j + 1]) {// 交换相邻元素,使之符合先小后大的顺序
arr[j] = arr[j] ^ arr[j + 1];
arr[j + 1] = arr[j] ^ arr[j + 1];
arr[j] = arr[j] ^ arr[j + 1];
}
}
}
return arr;
}
/*
* 设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,
* 故在进行下一趟排序时只要扫描到pos位置即可 好处:不用比较pos前面已经排好序的序列
*/
public static int[] bubbleSort_1(int arr[]) {
int i = arr.length - 1; // 初始时,最后位置保持不变
int pos = 0, j = 0;
while (i > 0) {
pos = 0; // 每趟开始时,无记录交换
for (j = 0; j < i; j++) {// 在有交换的位置之前使用冒泡排序,交换位置之后的数已经排好序了,就不用再比较;
if (arr[j] > arr[j + 1]) {
pos = j; // 记录交换的位置
arr[j] = arr[j] ^ arr[j + 1];
arr[j + 1] = arr[j] ^ arr[j + 1];
arr[j] = arr[j] ^ arr[j + 1];
}
}
i = pos; // 为下一趟排序作准备
}
return arr;
}
/*
* 插入排序:O(N^2)-稳定
*
* 插入排序是一个平均时间复杂度为O(N^2)的排序算法,它的算法思想是假设有一个数组长度为N,然后在[0..N-1]的范围内,
* 从i=1开始,和i-1的数进行比较,如果小则交换,并继续向前一个数比较,直到大则停止,i++。重复遍历直到i=N-1。
*/
public static int[] insertionSort(int[] A) {
for (int i = 1; i < A.length; i++) {
for (int j = i; j > 0; j--) {
if (A[j] < A[j - 1]) {
A[j] = A[j] ^ A[j - 1];
A[j - 1] = A[j] ^ A[j - 1];
A[j] = A[j] ^ A[j - 1];
}
}
}
return A;
}
public static int[] insertionSort_1(int[] A) {
for (int i = 1, j; i < A.length; i++) {
j = i;
while (j > 0 && A[j] < A[j - 1]) {// while循环去掉了A[j-1]<A[j]的无用比较,因为数组靠左的数已经排过序了
A[j] = A[j] ^ A[j - 1];
A[j - 1] = A[j] ^ A[j - 1];
A[j] = A[j] ^ A[j - 1];
j--;
}
}
return A;
}
/*
* 选择排序:O(N^2)-不稳定,但是可以做到稳定
*
* 算法描述
*
* 选择排序是一个平均时间复杂度为O(N^2)的排序算法,它的算法思想是假设有一个长度为N的数组,在[0...N-1]的范围上,从i=0开始,
* 遍历整个数组,记录最小值下标。一趟结束后和第一个位置交换,然后从i=1开始直到i=N-1结束
*/
public static int[] selectSort(int[] A) {
int n = A.length;
int smallest_index;
int i = 0, j = 0;
for (i = 0; i < n; i++) {
smallest_index = i;
for (j = i; j < n; j++) {
if (A[j] < A[smallest_index]) {
smallest_index = j;
}
}
if (A[smallest_index] != A[i]) {
A[i] = A[i] ^ A[smallest_index];
A[smallest_index] = A[i] ^ A[smallest_index];
A[i] = A[i] ^ A[smallest_index];
}
}
return A;
}
/*
* 二元选择排序,在一趟排序中从原本只记录一个最小值,改为同时记录最大值和最小值,使得排序的次数减少为N/2。
*/
public static int[] selectSort_1(int A[]) {
int n = A.length;
int i, j, min, max;
for (i = 1; i <= n / 2; i++) {
// 做不超过n/2趟选择排序
min = i;
max = i; // 分别记录最大和最小关键字记录位置
for (j = i + 1; j <= n - i; j++) {
if (A[j] > A[max]) {
max = j;
continue;
}
if (A[j] < A[min]) {
min = j;
}
}
if (A[min] != A[i - 1]) {
A[min] = A[min] ^ A[i - 1];
A[i - 1] = A[min] ^ A[i - 1];
A[min] = A[min] ^ A[i - 1];
}
if (A[max] != A[n - i]) {
A[max] = A[max] ^ A[n - i];
A[n - i] = A[max] ^ A[n - i];
A[max] = A[max] ^ A[n - i];
}
}
return A;
}
/*
* 归并排序:O(NlogN)-稳定
*
* 归并排序是一个平均时间复杂度为O(N*logN)的排序算法,它的算法思想有两种一种是递归的思想,一种是迭代的思想。
*
* 递归思想:
* 假设有一个长度为N的数组,在[0...N-1]的范围内对数组进行递归拆分。每次从当前数组的中间位置拆分成2部分,然后递归进行。第一次拆分后,分为[
* 0...N/2]
* 和[N/2+1....N-1]两部分,每部分包含N/2个元素。直到数组上界<数组下界停止递归。然后将这两个不能拆分的部分进行排序。
* ①开启辅助空间大小为两个元素个数之和 ②然后设置两个指针指向两个部分的起点 ③选择相对小的元素放入到辅助空间,并移动指针到下一位置
* ④重复步骤③直到某一指针到达序列尾 ⑤将另一序列剩下的所有元素直接复制到合并序列尾
*/
public static int[] mergeSort(int[] A) {
mergeSortCore(A, 0, A.length - 1);
return A;
}
public static void mergeSortCore(int[] A, int left, int right) {
if (right > left) {
int middle = (left + right) >> 1;
mergeSortCore(A, left, middle);
mergeSortCore(A, middle + 1, right);
merge(A, left, right, middle);
}
}
public static void merge(int[] A, int low, int high, int mid) {
int k = 0;
int[] temp = new int[high - low + 1];
int i = low, j = mid + 1;
while (i <= mid && j <= high) {
if (A[i] > A[j])
temp[k++] = A[j++];
else
temp[k++] = A[i++];
}
while (i <= mid)
temp[k++] = A[i++];
while (j <= high)
temp[k++] = A[j++];
for (k = 0, i = low; i <= high; i++, k++)
A[i] = temp[k];
}
/**
* 希尔排序:O(Nlog2N)-unstable
* 算法描述
* 希尔排序是一个平均时间复杂度为O(Nlog2N)的排序算法,它的算法思想是,
* 假设给定一个长度为N的数组。然后设置步长gap值(假设步长gap每次为数组长度的一半),
* 然后比较间隔gap的相邻两个元素的值,如果后者小,前者大则交换,也就是执行插入排序。遍历缩小gap,直到gap=1。
*/
public static int[] shellSort(int[] A) {
int n = A.length;
int gap = n / 2;//分隔的间隔长度 每次都取一半
while (gap >= 1) {
for (int i = gap; i < A.length; i++) {
int temp = A[i];
int j;
for (j = i - gap; j >= 0 && temp < A[j]; j = j - gap) {
A[j + gap] = A[j];
}
A[j + gap] = temp;
}
gap = gap / 2;
}
return A;
}
}
排序算法Java实现
最新推荐文章于 2023-06-27 14:16:23 发布