一、冒泡排序
基本介绍:
冒泡排序是比较基础的排序算法之一,其思想是相邻的元素两两比较,较大的数下沉,较小的数冒起来,这样一趟比较下来,最大(小)值就会排列在一端。整个过程如同气泡冒起,因此被称作冒泡排序。
步骤:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 每趟从第一对相邻元素开始,对每一对相邻元素作同样的工作,直到最后一对。
- 针对所有的元素重复以上的步骤,除了已排序过的元素(每趟排序后的最后一个元素),直到没有任何一对数字需要比较。
优化:
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。
public class BubbleSort {
public static void main(String[] args) {
// int[] arr = {3, 9, -1, 10, -2};
// System.out.println("排序前");
// System.out.println(Arrays.toString(arr));
// 测试冒泡排序速度O(n^2),给80000个数据,测试
int[] arr = new int[80000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 8000000); // 生成一个[0,8000000)数
}
long start = System.currentTimeMillis();
// 测试冒泡排序
bubbleSort(arr);
long end = System.currentTimeMillis();
System.out.println("排序的时间是=" + (end - start));
}
public static void bubbleSort(int[] arr) {
// 冒泡排序的时间复杂度O(n^2)
int temp = 0; // 临时变量
boolean flag = false; // 标识变量,表示是否进行过交换
for (int i = 0; i < arr.length - 1; i++) {
// 每次循环少一个:因为每循环一次就有一个已经排序好了
for (int j = 0; j < arr.length - 1 - i; j++) {
// 如果前面的数比后面的数大就交换
if (arr[j] > arr[j + 1]) {
flag = true; // 交换过
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if (!flag) { // 说明在一趟排序中,一次交换都没有发生
break;
} else {
flag = false; // 重置flag,进行下次判断
}
}
}
}
二、选择排序
基本介绍:
选择排序是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,继续放在起始位置直到未排序元素个数为0。
步骤:
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到未排序序列的起始位置。
- 重复第二步,直到所有元素均排序完毕。
public class SelectSort {
public static void main(String[] args) {
// int[] arr = {101, 34, 119, 1};
// System.out.println("排序前");
// System.out.println(Arrays.toString(arr));
int[] arr = new int[80000];
for (int i=0;i<arr.length;i++){
arr[i]= (int) (Math.random()*8000000); // 生成一个[0,8000000)数
}
long start = System.currentTimeMillis();
// 测试选择排序
selectSort(arr);
long end = System.currentTimeMillis();
System.out.println("排序的时间是=" + (end - start));
}
// 选择排序
public static void selectSort(int[] arr) {
for (int i=0;i<arr.length-1;i++){
int minIndex=i; // 记录最小值索引
int min=arr[i]; // 记录最小值
// 每次循环少一个:因为每循环一次就有一个已经排序好了
for (int j=i+1;j<arr.length;j++){
if (min>arr[j]){ // 如果最小值比当前值大则更新
min=arr[j];
minIndex=j;
}
}
// 避免重复 小优化 可判断也可不判断
if (minIndex!=i){
arr[minIndex]=arr[i];
arr[i]=min;
}
}
}
}
三、插入排序
基本介绍:
插入排序也是一种常见的排序算法,插入排序的思想是:将初始数据分为有序部分和无序部分,每一步将一个无序部分的数据插入到前面已经排好序的有序部分中,直到插完所有元素为止。
步骤:
每次从无序部分中取出一个元素,与有序部分中的元素从后向前依次进行比较,并找到合适的位置,将该元素插到有序组当中。
public class InsertSort {
public static void main(String[] args) {
// int[] arr = {101, 34, 119, 1};
int[] arr = new int[80000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 800000);
}
long start = System.currentTimeMillis();
// 测试插入排序
insertSort(arr);
long end = System.currentTimeMillis();
System.out.println("排序的时间是=" + (end - start));
}
// 插入排序
public static void insertSort(int[] arr) {
int insertVal=0; // 待插入的值
int insertIndex=0; // 应该插入的索引位置
for (int i = 1; i < arr.length; i++) {
// 定义待插入的数
insertVal = arr[i];
insertIndex = i - 1;
// 给insertVal找到插入的位置
// 说明
// 1.insertIndex>=0保证在给insertVal找出入位置,不越界
// 2.insertVal<arr[insertIndex] 待插入的数,还没有找到插入位置
// 3.就需要将arr[insertIndex] 后移
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
// 当退出while循环时,说明插入的位置找到,insertIndex+1
// 判断是否需要重置
if (insertIndex + 1 != i) {
arr[insertIndex + 1] = insertVal;
}
}
}
}
四、希尔排序
基本介绍:
希尔排序又称为缩小增量排序,是对之前介绍的插入排序的一种优化版本,优化的地方在于:不用每次插入一个元素时,就和序列中有序部分中的所有元素进行比较。该方法的基本思想是:设待排序元素序列有n个元素,首先取一个整数increment(小于序列元素总数)作为间隔,所有距离为increment的元素放在同一个逻辑数组中,在每一个逻辑数组中分别实行直接插入排序。然后缩小间隔increment,重复上述逻辑数组划分和排序工作。直到最后取increment=1,将所有元素放在同一个数组中排序为止。
步骤:
- 选increment,划分逻辑分组,组内进行直接插入排序。
- 不断缩小increment,继续组内进行插入排序。
- 直到increment=1,在包含所有元素的序列内进行直接插入排序。
public class ShellSort {
public static void main(String[] args) {
// int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
int[] arr = new int[80000000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 800000);
}
long start = System.currentTimeMillis();
// shellSort(arr); // 交换式
shellSort2(arr); // 移位式
long end = System.currentTimeMillis();
System.out.println("排序的时间是=" + (end - start));
}
// 交换法
public static void shellSort(int[] arr) {
int temp = 0;
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 遍历各组中所有的元素(共gap组,每组有..个元素),步长gap
for (int i = gap; i < arr.length; i++) {
// 同一组进行交换
for (int j = i - gap; j >= 0; j -= gap) {
if (arr[j] > arr[j + gap]) {
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
}
}
// 对交换式的希尔排序进行优化->移位法(组内插入排序)
public static void shellSort2(int[] arr) {
// 增量gap,并逐步缩小增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 从gap个元素开始,逐个对其所在的组进行直接插入排序
for (int i = gap; i < arr.length; i++) {
int j = i; // 要插入位置的索引
int temp = arr[j]; // 待插入的值
if (arr[j] < arr[j - gap]) {
while (j - gap >= 0 && temp < arr[j - gap]) {
// 移动
arr[j] = arr[j - gap];
j -= gap;
}
// 当退出while后,就给temp找到插入的位置
arr[j] = temp;
}
}
}
}
}
五、快速排序
基本介绍:
快速排序也是一种较为基础的排序算法,其效率比冒泡排序算法有大幅提升。因为使用冒泡排序时,一趟只能选出一个最值,有n个元素最多就要执行n - 1趟比较。而使用快速排序时,一次可以将所有元素按大小分成两堆,也就是平均情况下需要logn轮就可以完成排序。 快速排序的思想是:每趟排序时选出一个基准值,然后将所有元素与该基准值比较,并按大小分成左右两堆,然后递归执行该过程,直到所有元素都完成排序。
步骤:
- 先从数列中取出一个数作为基准数。
- 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
- 再对左右区间重复第二步,直到各区间只有一个数。
二路快排图解:
1、场景:对 6 1 2 7 9 3 4 5 10 8 这 10 个数进行排序
2、思路:
先找一个基准数(一个用来参照的数),为了方便,我们选最左边的 6,希望将 >6 的放到 6 的右边,<6 的放到 6 左边。
如:3 1 2 5 4 6 9 7 10 8
先假设需要将 6 挪到的位置为 k,k 左边的数 <6,右边的数 >6
(1)我们先从初始数列“6 1 2 7 9 3 4 5 10 8 ”的两端开始“探测 ”,先从右边往左找一个 <6 的数,再从左往右找一个 >6 的数,然后交换。我们用变量 i 和变量 j 指向序列的最左边和最右边。刚开始时最左边 i=0 指向 6,最右边 j=9 指向 8 ;
(2)现在设置的基准数是最左边的数,所以序列先右往左移动(j–),当找到一个 <6 的数(5)就停下来。接着序列从左往右移动(i++),直到找到一个 >6 的数又停下来(7);
(3)两者交换,结果:6 1 2 5 9 3 4 7 10 8;
(4)j 的位置继续向左移动(友情提示:每次都必须先从 j 的位置出发),发现 4 满足要求,接着 i++ 发现 9 满足要求,交换后的结果:6 1 2 5 4 3 9 7 10 8;
(5)目前 j 指向的值为 9,i 指向的值为 4,j-- 发现 3 符合要求,接着 i++ 发现 i=j,说明这一轮移动结束啦。现在将基准数 6 和 3 进行交换,结果:3 1 2 5 4 6 9 7 10 8;现在 6 左边的数都是 <6 的,而右边的数都是 >6 的,但游戏还没结束
(6)我们将 6 左边的数拿出来先:3 1 2 5 4,这次以 3 为基准数进行调整,使得 3 左边的数小于3,右边的数大于3,根据之前的模拟,这次的结果:2 1 3 5 4
(7)再将 2 1 抠出来重新整理,得到的结果: 1 2
(8)剩下右边的序列:9 7 10 8 也是这样来搞,最终的结果: 1 2 3 4 5 6 7 8 9 10 (具体看下图)
public class QuickSort {
public static void main(String[] args) {
int[] arr = new int[10000000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 10000);
}
long start = System.currentTimeMillis();
quickSort(arr, 0, arr.length - 1);
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
// System.out.println(Arrays.toString(arr));
}
/**
* @param arr 要排序的数组
* @param p 左指针0
* @param r 右指针arr.length
*/
public static void quickSort(int[] arr, int p, int r) {
if (p <= r) {
// 10000000万个数据测试
// int q = Partition1(arr, p, r); // 7112ms
// int q = Partition2(arr, p, r); // 2426ms
// quickSort(arr, p, q - 1); // 对左边排序
// quickSort(arr, q + 1, r); // 对右边排序
quick3(arr, p, r); // 578ms
// quick(arr, p, r); // 551ms
}
}
/**
* 单路快排
*
* @param arr 要排序的数组
* @param p 左指针0
* @param r 右指针arr.length
*/
public static int Partition1(int[] arr, int p, int r) {
int pivot = arr[p]; // 组元
int left = p + 1; // 左指针
int right = r; // 右指针
while (left <= right) {
if (arr[left] < pivot) {
left++;
} else {
swap(arr, left, right); // 交换
right--;
}
}
swap(arr, p, right);
return right;
}
/**
* 双路快排
*
* @param arr 要排序的数组
* @param p 左指针0
* @param r 右指针arr.length
* @return
*/
public static int Partition2(int[] arr, int p, int r) {
int pivot = arr[p]; // 组元
int left = p + 1; // 左指针
int right = r; // 右指针
// while (true) {
// while (left <= right && arr[left] <= pivot)
// left++; // 左指针右移寻找
// while (right >= left && arr[right] > pivot)
// right--; // 右指针左移寻找
// if (left > right) // 临界条件
// break;
// swap(arr, left, right); // 交换
// left++;
// right--;
// }
while (left <= right) {
while (left <= right && arr[left] <= pivot)
left++; // 左指针右移寻找
while (right >= left && arr[right] > pivot)
right--; // 右指针左移寻找
if (left < right) { // 临界条件
swap(arr, left, right); // 交换
left++;
right--;
}
}
swap(arr, p, right);
return right;
}
/**
* 三路快排
*
* @param arr 待排序的数组
* @param p 左指针0
* @param r 右指针arr.length
*/
public static void quick3(int[] arr, int p, int r) {
if (p > r) {
return;
}
int pivot = arr[p];
int left = p + 1; // 左指针
int e = p; // 相等区域的第一个元素
int right = r; // 右指针
while (left <= right) {
if (arr[left] < pivot) {
swap(arr, e++, left++);
} else if (arr[left] > pivot) {
swap(arr, left, right--);
} else {
left++;
}
}
quick3(arr, p, e - 1);
quick3(arr, right + 1, r);
}
//交换数组元素
public static void swap(int[] arr, int left, int right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
扩展:双指针遍历,分治,递归思想
奇左偶右
调整数组的顺序,使奇数在左,偶数在右
要求时间复杂度为O(n)
public class OddEven {
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
eachLeftRight(arr);
System.out.println(Arrays.toString(arr));
}
public static void eachLeftRight(int[] arr) {
int left = 0;//左指针
int right = arr.length - 1;//右指针
while (left <= right) {
while (left <= right && arr[left] % 2 == 1) {//左到右寻找偶数
left++;
}
while (left <= right && arr[right] % 2 == 0) {//右到左寻找奇数
right--;
}
if (left < right) {//找到后交换
swap(arr, left, right);
left++;
right--;
}
}
}
public static void swap(int[] arr, int left, int right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
最长递增子序列
(1,9,2,5,7,3,4,6,8,0)中最长的递增子序列为(3,4,6,8)
思路:利用两个指针,扫描记录
需要什么变量?
双指针遍历left,right
记录最长长度length
记录最长长度的开头索引下标index
public class 最长递增子序列 {
public static void main(String[] args) {
int[] arr = new int[]{1, 9, 2, 5, 7, 3, 4, 6, 8, 0};
int[] arr1 = new int[]{1, 9, 2, 5, 7, 3, 4, 6, 8, 9, 10, 12, 3, 4, 5};
theMaxLong(arr);
System.out.println();
theMaxLong(arr1);
}
public static void theMaxLong(int[] arr) {
int left = 0; // 定指针
int right = 1; // 动指针
int length = 0; // 记录长度
int index = 0; // 记录索引
while (right < arr.length) { // right遍历数组
while (right < arr.length && arr[right] > arr[right - 1]) { // 后一个数比前一个数大则右移
right++;
}
if (right - left > length) { // 刷新最长长度
length = right - left;
index = left; // 记录开始索引值
}
// 寻找下一个子序列
left = right;
right++;
}
// 遍历输出
for (int i = index; i < index + length; i++) {
System.out.print(arr[i] + " ");
}
}
}
第k个元素
以尽量高的效率求出一个乱序数组中按数值顺序的第k个元素值
法一:先排序,直接输出
法二:快排分区
public class theK {
public static void main(String[] args) {
int[] arr = {1, 3, 2, 4, 5, 67, 3, 7, 8, 9, 2, 10};
Scanner sc = new Scanner(System.in);
System.out.print("请输入k:");
int k = sc.nextInt();
int num = findK(arr, k, 0, arr.length - 1);
System.out.println(num);
}
public static int findK(int[] arr, int k, int p, int r) {
if (p <= r) {
int q = partition(arr, p, r);
int qK = q - p + 1;//主元是第几个元素
if (k > qK) {
return findK(arr, k-qK, q + 1, r);//要减去前半段
} else if (k < qK) {
return findK(arr, k, p, q - 1);
} else {
return arr[q];
}
}
return -1;
}
public static int partition(int[] arr, int p, int r) {
//三点中值法
int midIndex = 0;
if (arr[p] >= arr[r / 2] && arr[p] >= arr[r]) {
midIndex = p;
} else if (arr[r / 2] >= arr[p] && arr[r / 2] >= arr[r]) {
midIndex = r / 2;
} else if (arr[r] >= arr[p] && arr[r] >= arr[r / 2]) {
midIndex = r;
}
swap(arr, p, midIndex);
int pivot = arr[p];
int left = p + 1;//左
int right = r;//右
while (left <= right) {
while (left <= right && arr[left] <= pivot) {
left++;
}
while (left <= right && arr[right] > pivot) {
right--;
}
if (left < right) {
swap(arr, left, right);
}
}
swap(arr, p, right);
return right;
}
public static void swap(int[] arr, int left, int right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
六、归并排序
基本介绍:
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
步骤:
- 归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。
- 将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
- 将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。
public class MergeSort {
public static void main(String[] args) {
int[] arr = new int[10000000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 10000);
}
int[] temp = new int[arr.length];
long start = System.currentTimeMillis();
mergeSort(arr, 0, arr.length - 1, temp);
long end = System.currentTimeMillis();
System.out.println((end-start)+"ms");
// System.out.println(Arrays.toString(arr));
}
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int mid = left + ((right - left) >> 1);
// 向左递归
mergeSort(arr, left, mid, temp);
// 向右递归
mergeSort(arr, mid + 1, right, temp);
// 合并
merge(arr, left, right, mid, temp);
}
}
public static void merge(int[] arr, int left, int right, int mid, int[] temp) {
int i = left; // 左指针
int j = mid + 1; // 右指针
int t = 0; // 辅助指针
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[t++] = arr[i++];
} else {
temp[t++] = arr[j++];
}
}
// 剩余的 左边还没有放完
while (i <= mid) {
temp[t++] = arr[i++];
}
// 右边还没有放完
while (j <= right) {
temp[t++] = arr[j++];
}
// 拷贝
t = 0;
int tempLeft = left;
while (tempLeft <= right) {
arr[tempLeft++] = temp[t++];
}
}
}
扩展
合并有序数组
给定两个排序后的数组A和B,其中A的末端有足够的缓冲空间容纳B。编写一个方法,将B合并入A并排序。
public class MergeArr {
public static void main(String[] args) {
int[] A = new int[10];
int m=6;
for (int i = 0; i < m; i++) {
A[i] = i;
}
int[] B = {1, 3, 4, 5};
merge(A, B,m);
System.out.println(Arrays.toString(A));
}
public static void merge(int[] A, int[] B,int m) {
int cur = m + B.length - 1; // A数组容纳B数组后指向最后的元素
int left = m - 1; // 指向A数组最后的元素
int right = B.length - 1; // 指向B数组最后的元素
while (left >= 0 && right >= 0) {
if (A[left] < B[right]) {
A[cur--] = B[right--];
} else {
A[cur--] = A[left--];
}
}
while (left >= 0) {
A[cur--] = A[left--];
}
while (right >= 0) {
A[cur--] = B[right--];
}
}
}
逆序对个数
一个数列,如果左边的数大,右边的数小,则称这两个数位一个逆序对。求出一个数列中有多少个逆序对。
public class ReverseNum {
public static void main(String[] args) {
int[] arr = {2, 1, 3, 5, 1, 1};
int[] temp = new int[arr.length];
mergerSort(arr, 0, arr.length - 1, temp);
System.out.println(niXuCount);
System.out.println(Arrays.toString(arr));
// niXuDui(arr);
}
static int niXuCount = 0;
public static void mergerSort(int[] A, int left, int right, int[] temp) { // temp为临时数组
if (left < right) {
int midIndex = left + ((right - left) >> 1); // 中间索引
// 分
mergerSort(A, left, midIndex, temp);
mergerSort(A, midIndex + 1, right, temp);
// 合
merge(A, left, right, midIndex, temp);
}
}
// 暴力法
public static void niXuDui(int[] A) {
int count = 0; // 计数变量
for (int i = 0; i < A.length; i++) {
for (int j = i + 1; j < A.length; j++) {
if (A[i] > A[j]) {
count++;
}
}
}
System.out.println(count);
}
public static void merge(int[] A, int left, int right, int midIndex, int[] temp) {
int i = left; // 左指针
int j = midIndex + 1; // 右指针
int t = 0; // 指向临时变量
while (i <= midIndex && j <= right) { // 其中一边遍历完则退出
if (A[i] <= A[j]) {
temp[t++] = A[i++];
} else { // 右边小
temp[t++] = A[j++];
niXuCount += midIndex - i + 1; // 归并排序中加一行代码
}
}
// 左边未遍历完
while (i <= midIndex) {
temp[t++] = A[i++];
}
// 右边未遍历完
while (j <= right) {
temp[t++] = A[j++];
}
// 将temp复制回A中
t = 0;
int tempLeft = left;
while (tempLeft <= right) {
A[tempLeft++] = temp[t++];
}
}
}
七、堆排序
基本介绍:
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或大于)它的父节点。
步骤:
- 构造堆;
- 得到堆顶元素,这个值就是最大值;
- 交换堆顶元素和数组中的最后一个元素,此时所有元素中的最大元素已经放到合适的位置;
- 对堆进行调整,重新让除了最后一个元素的剩余元素中的最大值放到堆顶;
- 重复2~4这个步骤,直到堆中剩一个元素为止。
// 该代码是构造小顶推从大到小排序
public class HeapSort {
public static void main(String[] args) {
int[] arr = {1, 5, 2, 5, 3, 6, 7, 9, 4, 5};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void heapSort(int[] arr) {
// 构造小顶堆
minHeap(arr);
for (int i = arr.length - 1; i >= 0; i--) {
// 交换顶部元素和最后一个元素
swap(arr, 0, i);
// 减少数据堆化
minHeapFixDown(arr, 0, i - 1);
}
}
// 第一次构造小顶堆
public static void minHeap(int[] arr) {
int n = arr.length;
for (int i = n / 2 - 1; i >= 0; i--) {
minHeapFixDown(arr, i, n);
}
}
// 堆化
public static void minHeapFixDown(int[] arr, int i, int n) {
int left = 2 * i + 1; // 左子结点
int right = 2 * i + 2; // 右子结点
// 左子结点已经越界
if (left >= n) {
return;
}
int min = left;
// 右子结点越界
if (right >= n) {
min = left;
} else { // 没有越界
if (arr[right] < arr[left]) {
min = right;
}
}
// 根结点比子结点都要小,结束
if (arr[i] <= arr[min]) {
return;
}
// 交换
swap(arr, i, min);
// 小孩子那个位置的值发生了变化,i变更为小孩子那个位置,递归调整
minHeapFixDown(arr, min, n);
}
public static void swap(int[] arr, int left, int right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
扩展
小顶堆与topK问题
求海量数据(正整数)按逆序排列的前k个数(topK),因为数据量太大,不能全部
存储在内存中,只能一个一个地从磁盘或者网络上读取数据,请设计一个高效的算法
来解决这个问题。
第一行:用户输入k,代表要求topK
随后的n(不限制)行,每一行是一个整数代表用户输入的数据
请输出topK,从小到大,空格分割
public class 小顶堆与topK问题 {
static int[] heap;
static int index = 0;
static int k;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
k = sc.nextInt();
heap = new int[k];
int x = sc.nextInt();
while (x != -1) {
deal(x);//处理x
x = sc.nextInt();
}
printRs();
}
/**
* 如果数据量小于等于k,直接入堆中
* 等于k的时候进行堆化
*
* @param x
*/
public static void deal(int x) {
if (index < k) {
heap[index++] = x;
if (index == k) {
//堆化
minHeap(heap);
}
} else if (heap[0] < x) {//x和堆顶进行比较,如果x大于堆顶,x将堆顶挤掉并向下调整
heap[0] = x;
minHeapFixDown(heap, 0, k);
printRs();
}
}
public static void printRs() {
System.out.println(Arrays.toString(heap));
}
//对数组第一次堆化from n/2-1 to 0;
public static void minHeap(int[] arr) {
int n = arr.length;
for (int i = n / 2 - 1; i >= 0; i--) {
minHeapFixDown(arr, i, n);
}
}
//小顶堆,堆化
public static void minHeapFixDown(int[] arr, int i, int n) {
int left = 2 * i + 1;//左子结点
int right = 2 * i + 2;//右子结点
if (right >= n) {//越界
return;
}
int min;
if (arr[right] > arr[left]) {
min = left;
} else {
min = right;
}
if (arr[i] < min) {
return;//已经是小顶堆
}
swap(arr, i, min);
minHeapFixDown(arr, min, n);
}
public static void swap(int[] arr, int left, int right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
八、计数排序
基本介绍:
计数排序(Count Sort)是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。计数排序的思想是在给定的一组序列中,先找出该序列中的最大值和最小值,从而确定需要开辟多大的辅助空间,每一个数在对应的辅助空间中都有唯一的下标。
步骤:
- 找出序列中最大值,开辟Max+1的辅助空间
- 最小的数对应下标为0的位置,遇到一个数就给对应下标处的值+1,。
- 遍历一遍辅助空间,就可以得到有序的一组序列
public class CountSort {
public static void main(String[] args) {
int[] arr = new int[]{3, 2, 5, 2, 5, 1, 6, 7};
countSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void countSort(int[] arr) {
// 1.找出数组最大值Max
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (max < arr[i]) {
max = arr[i];
}
}
// 2.开辟一个辅助空间Max+1
int[] temp = new int[max + 1];
// 3.遍历原数组,在辅助数组中作记录
for (int i = 0; i < arr.length; i++) {
temp[arr[i]]++;
}
int index = 0;
// 4.遍历辅助数组
for (int i = 0; i < temp.length; i++) {
// 1)如果等于0,跳过
if (temp[i] == 0) {
continue;
}
// 2)如果不等于0,就赋值给原来数组对应的下标,对应值要--
while (temp[i] != 0) {
arr[index++] = i;
temp[i]--;
}
}
}
}
九、桶排序
基本介绍:
1)基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
2)基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法
3)基数排序(Radix Sort)是桶排序的扩展
4)基数排序是1887年赫尔曼·何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。
步骤:
将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
// 法一:
// 10个桶,每个桶装的数个数不定,适合用数组加ArrayList
private static ArrayList[] bucket=new ArrayList[10];
// 初始化桶
static{
for(int i=0;i<bucket.length;i++){
bucket[i]=new ArrayList();
}
}
// 排序
sort(A){
d=1;//入桶依据的位初始化为1
max=maxOf(A);//求出数组的最大值
/*
dNum=1;// 最大数据的位数
while(max/10!=0){
dNum++;
max/=10;
}
*/
dNum=(max+"").length;
while(d<=dNum){
//依据第二个参数入桶和出桶
sort(A,d++);
}
}
// 将数组A,按d这个位来分配和收集
sort(A,d){
// 全部入桶
for(i=0;i<A.length;i++){
//getDigitOn:获取第d位数字
putInBucket(A[i],getDigitOn(A[i],d));
}
// 每个桶中的数据依次压入原数组
k=0;
for(i=0;j<bucket.length;j++){
for(Object m:bucket[j]){
A[k++]=(Integer)m;
}
}
// 清空
clearAll();
}
// 获取第d位数字
getDigitOn(data,digitOn){
int n=1;
if(n<digitOn){
n*=((digitOn-n)*10);
}
return data/n%10;
}
// 最大值
maxOf(A){
max=A[0];
for(i=1;i<A.length;i++){
if(A[i]>max){
max=A[i];
}
}
}
putInBucket(data,digitOn){
switch(digitOn){
case 0:
bucket[0].add(data);
break;
case 1:
bucket[1].add(data);
break;
case 2:
bucket[2].add(data);
break;
case 3:
bucket[3].add(data);
break;
case 4:
bucket[4].add(data);
break;
case 5:
bucket[5].add(data);
break;
case 6:
bucket[6].add(data);
break;
case 7:
bucket[7].add(data);
break;
case 8:
bucket[8].add(data);
break;
case 9:
bucket[9].add(data);
break;
}
}
clearAll(){
for(ArrayList b:bucket){
b.clear();
}
}
// 法二:
sort(A){
// 得到数组中最大的数的位数
int max=A[0];
for(int i=1;i<A.length;i++){
if(A[i]>max){
max=A[i];
}
}
// 得到最大数是几位数
int maxLength=(""+max).length;
/*int maxLength=1;
while(max/10!=0){
maxLength++;
max/=10;
}*/
// 定义一个二维数组表示10个桶,每个桶是一个一维数组
// 说明
// 1.二维数组包含10个一维数组
// 2.为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
// 3.很明确,基数排序是使用空间换时间的经典算法
int[][] bucket=new int[10][A.length];
// 为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
int[] bucketElementCounts=new int[10];
for(int i=0,n=1;i<maxLength;i++,n*=10){
for(int j=0;j<A.length;j++){
// 针对每个元素的对应位进行排序处理,第一次是个位,第二次是十位...
// 取出每个元素的对应位的值
int dealNum=A[j]/n%10;
// 放入到对应的桶中
bucket[dealNum][bucketElementCounts[dealNum]]=dealNum;
bucketElementCounts[dealNum]++;
}
// 照这个桶的顺序(一维数组的下标一次取出数据,放入原来数组)
int index=0;
// 遍历每一桶,并将桶中的数据放入到原数组
for(int k=0;k<bucket.length;k++){
// 如果桶中有数据,我们才放入到原数组
if(bucketElementCounts[k]!=0){
// 循环该桶,即第k个桶(即第k个一维数组),放入
for(int l=0;l<bucketElementCounts[k].length;l++){
// 取出元素放入到arr
A[index++]=bucket[k][l];
}
}
// 第i+1处理后需要将每个bucketElementCounts[k]=0!!!
bucketElementCounts[k]=0;
}
}
}
十、基数排序
基础介绍:
基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),为数组长度,k为数组中的数的最大的位数;基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
步骤:
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点);
十一、扩展:java特殊排序
特殊排序
输入一个正整数数组,把数组里所有整数拼接起来排成一个数,打印出能拼接出的所有数字中最小的一个。
例如输入数组{3,32,321},则打印出这3个数字能排成的最小数字为:321323。两两组合得到的数较小进行排序
public class SpecialOrder {
public static void main(String[] args) {
Integer[] arr = {3, 32, 321};
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
//o1:后一个 o2:前一个
String s1 = o1 + "" + o2;//323
String s2 = o2 + "" + o1;//332
return s1.compareTo(s2);//-1 逆序
}
});
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
sb.append(arr[i]);
}
System.out.println(Integer.parseInt(sb.toString()));
}
}