文章目录
分治思想
1.分解
2.解决子问题
3.合并
题目: 数组排序
一:插入排序
1. 直接插入排序
public void insertSort(int[] arr, int len) {
int i, j;
for (i = 2; i < len; i++) {
if (arr[i] < arr[i - 1]) {
arr[0] = arr[i];//arr[0]为哨兵,不作排序的元素
for (j = i - 1; arr[j] > arr[0]; j--) {
arr[j + 1] = arr[j];
}
arr[j + 1] = arr[0];
}
}
}
2. 折半插入排序
public void insertSort02(int[] arr, int len) {
int i, j, low, high, mid = 0;
for (i = 2; i < len; i++) {
if (arr[i] < arr[i - 1]) {
low = 1;
high = i;
arr[0] = arr[i];//arr[0]不作为排序元素
while (low <= high) {//二分搜索,注意临界条件
mid = low + ((high - low) >> 1);
if (arr[mid] < arr[0]) low = mid + 1;
else high = mid - 1;
}
for (j = i - 1; j >= high + 1; j--)
arr[j + 1] = arr[j];
arr[high + 1] = arr[0];
}
}
}
3. 希尔排序
public void shellSort(int[] arr, int len) {
int i, j, tmp;
for (int dk = len / 2; dk >= 1; dk /= 2) //步长,最小为1
for (i = dk; i < len; i++) // 接下来就是直接插入排序
if (arr[i] < arr[i - dk]) {
tmp = arr[i]; //暂存于tmp中
for (j = i - dk; j >= 0 && arr[j] > tmp; j -= dk)
arr[j + dk] = arr[j];
arr[j+dk] = tmp;
}
}
交换排序
1. 冒泡排序
public void bubbleSort(int[] arr, int len) {
int i, j;
boolean flag = false;
for (i = len - 1; i >= 0; i--) {
for (j = 0; j < i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
flag = true;
}
}
if(!flag)
break;
}
}
2. 快速排序
双向扫描分区法
注意临界条件的判断
import java.util.Arrays;
class Solution {
public int[] sortArray(int[] nums) {
quickSort(nums, 0, nums.length - 1);
return nums;
}
private void quickSort(int[] nums, int start, int end) {
if (start < end) {
int pos = partition(nums, start, end);
quickSort(nums, start, pos - 1);
quickSort(nums, pos + 1, end);
}
}
private int partition(int[] nums, int start, int end) {
int base = start;
int left = base + 1;
int right = end;
while(left <= right){//在我的算法中,临界条件全部是left<=right,一定要注意。
while (left <= right && nums[left] <= nums[base]){
left++;
}
while (left <= right && nums[right] >= nums[base]){
right--;
}
if(left < right)
swap(nums,left,right);
}
swap(nums,base,right);
return right;
}
private void swap(int[] nums, int base, int right) {
int t = nums[base];
nums[base] = nums[right];
nums[right] = t;
}
}
可优化的地方: 优化的地方在于base
的取值
选择排序
1. 简单选择排序
思想: 每i
趟在下标为i
的后面数组中的数字找找到最小的数字,并且与arr[i]
比较。如果小就交换。
这样每次就确定了第i
个数的位置,以后都不改变
public void selectSort(int[] arr, int n) {
int min;
for (int i = 0; i < n; i++) {
min = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[min])
min = j;
}
if (min != i) swap(arr, min, i);
}
}
2. 堆排序
-
什么是堆
概念: 二叉堆是一个完全二叉树
特性:
1)
. 父节点的键值总是大于等于(或者小于等于)任何一个子节点的键值
2)
. 每个节点的左右子树都是一个二叉堆(要么是大顶堆要么是小顶堆) -
思想:
2.1 把数组大/小顶堆化(因为叶子结点不具有孩子节点,所以已经满足堆的要求了。所以要从A.lenth/2 - 1
的位置开始调用辅助构造方法来保证该节点作为根节点时,对于整个堆来说,这个局部时大顶堆或者小顶堆)
2.2 设计辅助构造大小顶堆的方法
传入数组,局部根节点的索引,和数组最后节点的索引。来保证局部是大/小顶堆
2.3. 让根节点每次都和最后一个元素交换,但是比较完一次后,数组的最后一个元素要减1
。然后调用辅助构造大/小顶堆的方法,重新构造出一个长度减1的大/小顶堆。这样数组从最后一个位置开始依次往前被填充有序数据。直到整个数组有序
import java.util.Arrays;
class Solution {
public int[] sortArray(int[] nums) {
heapSort(nums);
return nums;
}
private void heapSort(int[] nums) {
int len = nums.length;
for (int i = len / 2 - 1; i >= 0; i--)
maxHeapize(nums, i, len - 1);
while (len > 0) {
len -= 1;
swap(nums, 0, len);
maxHeapize(nums, 0, len - 1);
}
}
private void maxHeapize(int[] nums, int root, int len) {
int lch = root * 2 + 1;
int rch = root * 2 + 2;
if (rch > len) {//说明无右孩子
if (lch > len) {//说明也无左孩子,那么就是叶子节点
return;
}
//存在左孩子,而无右孩子
if (nums[lch] < nums[root])
return;
swap(nums, lch, root);
maxHeapize(nums, lch, len);//左孩子继续向下调整
} else {
//说明有左右孩子,注意不存在只有右孩子,没有左孩子的情况。
int max = Math.max(nums[lch], nums[rch]);
if (nums[root] >= max) return;
if (nums[lch] > nums[rch]) {
swap(nums, lch, root);
maxHeapize(nums, lch, len);
} else {
swap(nums, rch, root);
maxHeapize(nums, rch, len);
}
}
}
private void swap(int[] array, int a, int b) {
int tmp = array[a];
array[a] = array[b];
array[b] = tmp;
}
}
归并排序和基数排序
1. 归并排序
-
和快速排序的区别
1.不用把大的和小的分区
2.需要新的辅助空间(拷贝原数组)
3.归并排序的有序性是靠分别比较两个区的头位置
merge
的参数
void merge(arr,start,mid,end)
import java.util.Arrays;
class Solution {
public int[] sortArray(int[] nums) {
mergeSort(nums, 0, nums.length - 1);
return nums;
}
private void mergeSort(int[] nums, int start, int end) {
if (start < end) {
int mid = start + ((end - start) >> 1);
mergeSort(nums, start, mid);
mergeSort(nums, mid + 1, end);
merge(nums, start, mid, end);
}
}
public void merge(int[] arr, int L, int mid, int R) {
int[] temp = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = mid + 1;
// 比较左右两部分的元素,哪个小,把那个元素填入temp中
while (p1 <= mid && p2 <= R) {
temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
// 上面的循环退出后,把剩余的元素依次填入到temp中
// 以下两个while只有一个会执行
while (p1 <= mid) {
temp[i++] = arr[p1++];
}
while (p2 <= R) {
temp[i++] = arr[p2++];
}
// 把最终的排序的结果复制给原数组
for (i = 0; i < temp.length; i++) {
arr[L + i] = temp[i];
}
}
public static void main(String[] args) {
int[] nums = {1, 4, 5, 2, 6, 9, 8, 7};
System.out.println(Arrays.toString(new Solution().sortArray(nums)));
}
}
2. 基数排序
思想
第一次按个位数将各个数字分配到0~9号桶当中然后出桶
第二次按十位数将各个数字分配到0~9号桶当中然后出桶。。。。。依次类推
通常用于十进制数字
class Solution {
public int[] radixSort(int[] nums) {
int[] res = fixAndGetMaxLen(nums);//基数排序解决存在负数的情况,先调整数组
int min = res[0];
int size = res[1];
int[][] buckets = new int[10][nums.length];
int[] p = new int[10];//记录每个桶的最后一个元素的位置
int n = 1;
for (int i = 0; i < size; i++) {//进行n次按关键字排序, 采取的方式:LSD(最低为优先)
for (int j = 0; j < nums.length; j++) {
int m = (nums[j] / n) % 10;
buckets[m][p[m]] = nums[j];
p[m]++;//指向桶中存下一个数的位置
}
int index = 0;
for (int j = 0; j < p.length; j++)
if (p[j] != 0) {
for (int k = 0; k < p[j]; k++)
nums[index++] = buckets[j][k];
p[j] = 0;//相当于清空该桶中元素
}
n *= 10;
}
if(min < 0)
for (int i = 0; i < nums.length; i++)
nums[i] += min;
return nums;
}
private int[] fixAndGetMaxLen(int[] nums) {
int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
for (int n : nums) {
max = Math.max(max, n);
min = Math.min(min, n);
}
if (min < 0) {
for (int i = 0; i < nums.length; i++)
nums[i] -= min;//每个数减去最小的那个负数。使得数组中每个数都 >=0
max -= min; //相应的数组中的最大值也要减去min,才是现在数组的最小值。
}
return new int[]{min, (max + "").length()};//返回max是几位数 与 min
}
}
桶排序和计数排序
1. 计数排序
思想
把数组的值映射到一个辅助空间的索引上,每次 +1;
缺点
如果数据特别的稀疏,那么会浪费大量的空间
class Solution {
public int[] sortArray(int[] nums) {
countSort(nums);
return nums;
}
private void countSort(int[] nums) {
int[] store = new int[100001];//这里开辟100001的原因是题目中说明了数据大小的范围是[-50000,50000];
for (int i = 0; i < nums.length; i++)
store[nums[i] + 50000]++;
int current = 0;
for (int i = 0; i < store.length; i++) {
if (store[i] != 0) {
while (store[i]!=0) {
nums[current++] = i - 50000;
store[i]--;
}
}
}
}
}
2. 桶排序
思想
将数组中的数按照一定的运算规则(不固定)分配到一些桶当中。保证桶中的数据是有序的,然后依次出桶(从桶中把数据取出来)
说明:
桶排序更是对计数排序的改进,计数排序申请的额外空间跨度从最小元素值到最大元素值,若待排序集合中元素不是依次递增的,则必然有空间浪费情况。桶排序则是弱化了这种浪费情况,将最小值到最大值之间的每一个位置申请空间,更新为最小值到最大值之间每一个固定区域申请空间,尽量减少了元素值大小不连续情况下的空间浪费情况。
import java.util.Arrays;
class Solution {
public int[] sortArray(int[] nums) {
bucketSort(nums);
return nums;
}
public void bucketSort(int[] nums) {
//1.找最大值,同于计算元素在几号桶
int len = nums.length;
Node[] buckets = new Node[len];
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int i = 0; i < nums.length; i++){
max = Math.max(max,nums[i]);
min = Math.min(min,nums[i]);
}
if(min < 0){//处理负数
for (int i = 0; i < nums.length; i++)
nums[i] -= min;
max -= min;
}
//2.入桶
for (int i = 0; i < nums.length; i++) {
//得到每个元素应该在几号桶里 e*len/(max+1)
int index = nums[i] * len / (max + 1);
if (buckets[index] == null) {//如果该桶中还没有元素
buckets[index] = new Node(nums[i]);
} else {//该桶有多个元素,应该按序插入
insertTo(buckets[index], nums[i]);//把头指针和要插入的元素传入参数
}
}
int index = 0;
//3.出桶
for (int i = 0; i < buckets.length; i++) {
if (buckets[i] != null) {
Node p = buckets[i];
while (p != null) {
if(min < 0)
nums[index++] = p.data + min;
else
nums[index++] = p.data;
p = p.next;
}
}
}
}
private void insertTo(Node head, int e) {
Node p = head.next;
Node pre = head;
Node newNode = new Node(e);
while (p != null) {
if (p.data > e) {//找到第一个大于e的元素的位置
pre.next = newNode;
newNode.next = p;
return;
}
pre = p;
p = p.next;
}
//如果一直到最后一个元素也没有大于当前元素
if (pre.data > e) {
int tmp = newNode.data;
newNode.data = pre.data;
pre.data = tmp;
pre.next = newNode;
newNode.next = p;
} else {
pre.next = newNode;
}
}
}
class Node {
int data;
Node next;
public Node(int data) {
this.data = data;
}
}