1. 冒泡排序
算法思路:
第一趟,通过两两交换的手段,将最大元素顶到最末端
第二趟,将次大元素顶到倒数第二个位置
…………………………………………
时间复杂度:O(n²)
空间复杂度:O(1)
原址排序
稳定性:有相同元素,排序前和排序后相对位置不会变化,稳定
代码:
package 排序;
/**
* @author: DreamCode
* @file: 冒泡排序.java
* @time: 2022-4-8-15:26:06
*/
public class 冒泡排序 {
public static void main(String[] args) {
int[] arr = { 3, 5, 7, 1, 2, 0, 2, 1, 5, 10, 22, 6 };
BubbleSort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
private static void BubbleSort(int[] arr) {
// TODO 冒泡排序
int len = arr.length;
for (int i = 0; i < len; i++) {
for (int j = 0; j < len - i - 1; j++) { //注意上边界,泡都从第一个开始冒,但是每一趟完成之后,天花板在降低
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
2. 选择排序
算法思路:
思路:第一趟,选择所有元素中最小的,和第一位交换
第二趟,选择第二位及以后所有元素中最小的,和第二位交换
……
时间复杂度:O(n²)
空间复杂度:O(1)
原址排序
稳定性:考虑32211,第一趟标记最末1为最小→1(4)221(3)3;第二趟标记原下标3的1为最小→1(4)1(3)223
两个1的相对位置发生了变化,因此不稳定
代码:
package 排序;
/**
* @author: DreamCode
* @file: 选择排序.java
* @time: 2022-4-8-15:39:50
*/
public class 选择排序 {
public static void main(String[] args) {
int[] arr = { 3, 5, 7, 1, 2, 0, 2, 1, 5, 10, 22, 6 };
SellectionSort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
private static void SellectionSort(int[] arr) {
// TODO 选择排序
int len = arr.length;
for (int i = 0; i < len; i++) {
int minIndex = i;
for (int j = i; j < len; j++) { // 从i~len中找到最小的数放在第i位置
if (arr[j] < arr[minIndex]) { // 当前位置数比最小的数还小,更新
minIndex = j;
}
}
int temp = arr[minIndex]; // 交换
arr[minIndex] = arr[i];
arr[i] = temp;
}
}
}
3. 插入排序
算法思路:
假设某元素之前的子序列有序,该元素如果大于子序列末端元素则可继续下一个元素;如果该元素小于子序列末端,则往前插入到指定位置
第一趟:第一个元素已经有序
第二趟:第二个元素往前插
第三趟:第三个元素往前两个元素插
……………………………………时间复杂度:O(n²)=1+2+3+…+n-1
空间复杂度:O(1)
原址排序
稳定性:由于是从后往前,后续元素如果存在前面相等的,无法越过,相对位置不会发生变化
代码:
package 排序;
/**
* @author: DreamCode
* @file: 插入排序.java
* @time: 2022-4-8-15:47:58
*/
public class 插入排序 {
public static void main(String[] args) {
int[] arr = { 3, 5, 7, 1, 2, 0, 2, 1, 5, 10, 22, 6 };
InsertionSort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
private static void InsertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int temp = arr[i]; // 保存每次需要插入的那个数
int j;
for (j = i; j > 0 && arr[j - 1] > temp; j--) { // 寻找当前数可以插的位置
arr[j] = arr[j - 1]; // 把大于需要插入的数往后移动。最后不大于temp的数就空出来j
}
arr[j] = temp; // 将需要插入的数放入这个位置
}
}
}
4. 堆排序
算法描述:
首先要知道大顶堆和小顶堆,数组就是一个堆,每个i节点的左右孩子是2i+1和2i+2
有了堆,将其堆化:从n/2-1个元素开始向下修复,将每个节点修复为小(大)顶堆
修复完成后,数组具有小(大)顶堆的性质
按序输出:小顶堆可以对数组逆序排序,每次交换栈顶和末尾元素,对栈顶进行向下修复,这样次小元素又到堆顶了
时间复杂度: 堆化:一半的元素修复,修复是单分支的,所以整体堆化为nlgn/2
排序:n个元素都要取出,因此调整n次,每次调整修复同上是lgn的,整体为nlgn 空间复杂度:不需要开辟辅助空间
原址排序
稳定性:
代码:
package 堆排序;
/**
* @author: DreamCode
* @file: 堆排序.java
* @time: 2022年3月7日-上午8:34:39
*/
public class 堆排序 {
public static void main(String[] args) {
int[] arr = { 1, 3, 5, 2, 6, 4, 8, 1 };
sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
private static void sort(int[] arr) {
// TODO 堆排序
int n = arr.length;
// 进行堆化
MinHeap(arr);
// 进行排序
for (int i = n - 1; i >= 0; i--) { //每次交换堆顶和末尾元素,对栈顶进行向下修复,这样次小元素又到堆顶了
swap(arr, 0, i);
MinHeapFixDown(arr, 0, i - 1); //每一次能确定最后一个元素,这里每一轮能确定一个最大值
}
}
private static void MinHeap(int[] arr) {
// TODO 进行堆化
int n = arr.length;
for (int i = n / 2 - 1; i >= 0; i--) {
// TODO 实现每一个数都是堆
MinHeapFixDown(arr, i, n); //堆修复,节点下沉
}
}
private static void MinHeapFixDown(int[] arr, int i, int n) {
// TODO 节点下沉
// 判断是否越界
int left = 2 * i;
int right = 2 * i + 1;
if (left >= n) { // 左节点越界,没有左右孩子,当前堆只有一个元素,即为叶子节点
return;
}
// 获取左右节点中的最小值
int min = left;
if (right >= n) { // 右节点越界
min = left;
return;
} else {
min = arr[left] <= arr[right] ? left : right;
}
if (arr[i] > arr[min]) {
// 将节点与最小值交换,递归进行节点下沉
swap(arr, i, min);
MinHeapFixDown(arr, min, n);
}
}
private static void swap(int[] arr, int i, int min) {
// TODO 两数交换
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
PriorityQueue为最优队列容器类,里面维护的即一个堆
4. TopK问题
问题描述:
求海量数据(正整数)按逆序排列的前k个数(topK),因为数据量太大,不能全部存储在内存中,只能一个一个地从磁盘或者网络上读取数据,
请设计一个高效的算法来解决这个问题 第一行:用户输入K,代表要求得topK 随后的N(不限制)行,每一行是一个整数代表用户输入的数据
用户输入-1代表输入终止 请输出topK,从小到大,空格分割
思路解析:
堆排序的应用,小根堆每一轮排序能确定一个最大的值
package 排序;
import java.util.Scanner;
/**
* @author: DreamCode
* @file: TopK.java
* @time: 2022年3月7日-下午2:08:09
*/
public class TopK {
static int k;
static int[] heap;
static int index; //记录当前为第几个top
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
k = scanner.nextInt();
heap = new int[k];
int x = scanner.nextInt();
while (x != -1) {
deal(x);
x = scanner.nextInt();
}
}
private static void deal(int x) {
if (index < k) { //当前的数目小于k个数
heap[index++] = x;
if (index == k) { //等于k个数,直接堆化
MinHeap(); // 堆化
}
} else { //再次加入新的数,此时直接跟堆顶比较,因为堆顶为最小的值,所以只有比堆顶大才有机会进入前topK个数
if (heap[0] < x) {
heap[0] = x; //更新堆顶
MinHeapFixDown(heap,0, k); //重新修复堆
}
printHeap();
}
}
private static void printHeap() {
// TODO 打印堆
int[] heapCopy = new int[k];
System.arraycopy(heap, 0, heapCopy, 0, k);
sort(heapCopy); //对堆排序
for (int i = 0; i < k; i++) {
System.out.print(heapCopy[i] + " ");
}
System.out.println();
for (int i = 0; i < k; i++) {
System.out.print(heap[i] + " ");
}
}
private static void sort(int[] heapCopy) {
// TODO 堆排序
int n = heapCopy.length;
for(int i=n-1;i>=0;i--) {
swap(heapCopy,0, i);
MinHeapFixDown(heapCopy,0, i-1);
}
}
private static void MinHeap() {
// TODO 对heap进行堆化
int n = heap.length;
for (int i = n / 2 - 1; i >= 0; i--) {
MinHeapFixDown(heap,i, n);
}
}
private static void MinHeapFixDown(int []arr,int i, int n) {
// TODO 堆节点下沉
int left = 2 * i;
int right = 2 * i + 1;
int min = left;
if (left >= n) { // 当前节点为叶子节点
return;
} else {
if (right < n) { // 左右儿子均存在
min = arr[left] > arr[right] ? right : left;
}
}
if (arr[i] > arr[min]) {
swap(arr,i, min); // 与最小值进行交换
MinHeapFixDown(arr,min, n);
}
}
private static void swap(int[]arr,int i, int min) {
// TODO 交换两数
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
5. 排序数组中找指定和的因子
问题描述:
在一个排序数组中,找指定和的两个因子
思路解析:
双指针遍历
package 排序;
/**
* @author: DreamCode
* @file: 排序数组中找和的因子.java
* @time: 2022年3月7日-上午10:23:06
*/
public class 排序数组中找和的因子 {
public static void main(String[] args) {
int[] arr = {-8,-4,0,2,4,5,8,9,10};
int k=10;
int left=0,right=arr.length-1;
while(left<right) {
if(arr[left]+arr[right]>k) {
right--;
}else if (arr[left]+arr[right]<k) {
left++;
}else {
System.out.println(arr[left]+" "+arr[right]);
left++;
}
}
}
}
6. 判断数组的包含问题
问题描述:
判断字符串A中的字符是否全部出现在字符串B中
思路解析:
将字符串A转化为字符数组,采用二分搜索算法,判断A中的每一个字符是否存在于B中
package 排序;
import java.util.Arrays;
/**
* @author: DreamCode
* @file: 判断数组的包含问题.java
* @time: 2022年3月7日-下午9:22:15
*/
public class 判断数组的包含问题 {
private static boolean check(String s1, String s2) {
char[] s2_char = s2.toCharArray();
Arrays.sort(s2_char);
for(int i = 0;i<s1.length();i++) {
char a = s1.charAt(i);
int pos = Arrays.binarySearch(s2_char, a);
if(pos<0) {
System.out.println(pos);
return false;
}
}
return true;
}
public static void main(String[] args) {
String s1 = "asdfq";
String s2 = "dafse";
System.out.println(check(s1, s2));
}
}
7. 数组能排成的最小数
问题描述:
给定一个int数组arr,将int数组arr中的数组合成最小的整数
思路解析:
将int数组arr按照字符的字典序进行排序,这样能保证最前面的数字段序是最小的,然后从前到后进行字符拼接,此时拼接出来的数即位最小的数
package 排序;
import java.util.Arrays;
import java.util.Comparator;
/**
* @author: DreamCode
* @file: 数组能排成的最小数.java
* @time: 2022年3月7日-下午9:07:06
*/
public class 数组能排成的最小数 {
public static void main(String[] args) {
Integer[] arr = { 3, 32, 321 };
int res = f(arr);
System.out.println(res);
}
private static int f(Integer[] arr) {
// TODO 特殊排序
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// TODO o1对于o2:小于、等于、大于分别返回-1,0,1
String s1 = o1 + "" + o2;
String s2 = o2 + "" + o1;
return s1.compareTo(s2);
}
});
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
stringBuilder.append(arr[i]);
}
return Integer.parseInt(stringBuilder.toString());
}
}
8. 需排序的子数组长度
问题描述:
有一个整数数组,存在索引a和b,只要将a和b之间的元素排好序,整个数组就是有序(升序)的。 注意:b-a应该越小越好,也就是说,找出符合条件的最短序列。
给定一个int数组A和数组的大小n,请输出a和b,若原数组有序,输出0和0。保证A中元素均为正整数。
测试样例: 6 1 4 6 5 9 10 输出:2 3
思路解析:
分别记录两边需要排序元素的边界即可。具体来说就是:
从左往右遍历,记录左侧出现的最大值,记为 max ,如果当前数字 a[i] < max,说明若排序,max 必然会挪到 a[i] 右边,记录最右边出现这种情况的位置;
从右往左遍历,记录右侧出现的最小值,记为 min ,如果当前数字 a[i] > min, 说明若排序,min 必然会挪到 a[i] 左边,记录最左边出现这种情况的位置。
这样得到的两个坐标之间的数字就是需要排序的子数组。
package 排序;
/**
* @author: DreamCode
* @file: 需排序的子数组长度.java
* @time: 2022年3月7日-上午10:55:14
*/
public class 需排序的子数组长度 {
public static void main(String[] args) {
int[] arr = {2,9,4,6,5,9,10};
int[] res = findsigment(arr);
System.out.println(res[0]+" "+res[1]);
}
private static int[] findsigment(int[] arr) {
int n = arr.length;
int min = Integer.MAX_VALUE,max=Integer.MIN_VALUE,p1=-1,p2=-1;
for(int i=0;i<n;i++) {
if(arr[i]>max) { //更新最大值
max=arr[i];
}
if(arr[i]<max) { //当前值比最大值小,说明排序后最大值必然会在该值之后
p2=i;
}
}
for(int i=n-1;i>=0;i--) {
if(arr[i]<min) { //更新最小值
min=arr[i];
}
if(arr[i]>min) { //当前值比最小值大,说明排序后最小值必然会在该值的之前
p1=i;
}
}
if(p1==-1) {
return new int[] {0,0};
}
return new int[]{p1,p2};
}
}