通用方法
public static void print(int[] A) {
for (int i = 0; i < A.length; i++) {
System.out.print(A[i] + " ");
}
System.out.println(" ");
}
//交换数组的两个元素
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static int[] getMaxAndMin(int[] A) {
int max = A[0]; // 使用数组的第一个元素初始化max
int min = A[0];
for (int i = 1; i < A.length; i++) { // 从数组的第二个元素开始遍历
if (A[i] > max) {
max = A[i];
}
if (A[i] < min) {
min = A[i];
}
}
return new int[]{max, min};
}
//得到data特定位数上的数字
public static int getDigitData(int data, int d) {
while (d - 1 > 0) {
data /= 10;
d--;
}
return data % 10;
}
树
顺序存储
给一个节点,下标为i
他的子节点下标是(2i + 1)和(2i + 2)
他的父节点下标是(i - 1) / 2
先序、中序、后序遍历
public static void preOrder(int[] arr,int index) {
// 终止条件
if (index >= arr.length) {
return;
}
// 根节点
System.out.println(arr[index]);
// 左右子节点
preOrder(arr,(2 * index + 1));
preOrder(arr,(2 * index + 2));
// 中序遍历就是把sout放preOrder中间
// 后序遍历就是把sout放preOrder后面
}
堆
堆排序(不常用)
时间复杂度O(nlgn),含的常数大
没有快排和分治快
小顶堆
排完是降序数组
1堆化
从最后一个非叶子节点开始,向上构建最小堆 即:从i = A.length/2 - 1一直到0,对每一个元素进行检验排序。
显然,他是倒着检验排序的(先处理下面的枝叶,再处理上面的,最后处理根,一组一组处理,一组里含一个根和他的叶子节点
如果A[i]比他的两个孩子都小,就不用调整
否则,找到两个孩子中较小的那个,与A[i]交换
i变为发生交换的那个孩子位置,再进行检验交换(递归),没发生位置变化的不用检验
2按序输出元素
顶端数是最小数,末位数一般是最大数,但不绝对。
将顶端数与末位数交换,此时,末位数为最小数,剩余待排序数组个数为n-1
将n-1个数再构造成小根堆
再重复上述步骤(交换,构造……)
public static void MinHeap(int[] A) {
int n = A.length;
for (int i = n/2 - 1; i >= 0; i--) {
MinHeapFixDown(A,i,n);
}
}
//递归处理i位置的元素及其所有叶子节点
//每次递归处理一个根和紧挨着的叶子节点(最多两个)
public static void MinHeapFixDown(int[] A, int i,int n) {
// 找到i的左右孩子
int left = 2 * i + 1;
int right = 2 * i + 2;
// 递归结束条件:i是叶子节点
// 想要i是叶子节点,就要i没有左右孩子
// 左孩子比右孩子小,如果左孩子没有了,肯定没有右孩子
// 写等于号的原因是
// 如果当前left是堆的最后一个元素,没有叶子节点,在数组里是最后一个索引,那么他也不用检验并处理
if (left >= n) {
// 左孩子越界
return;
}
// 如果左孩子没越界
// 就只剩下两种可能:只有左孩子或者左右孩子都有
// 较小孩子初始化left
int min = left;
// 如果右孩子越界
// 较小的孩子一定是左孩子,不用写
if (right < n) {
// 如果左右孩子都有,min赋值较小的一方
// 因为已经默认min = left;,所以只需考虑right是较小的一种情况
if (A[right] < A[left]) {
min = right;
}
}
// 如果A[i]小于两个孩子,就不用调整
if (A[i] <= A[min]) {
return;
}
// 否则,与较小的孩子交换
int temp = A[i];
A[i] = A[min];
A[min] = temp;
// 这个函数检验的是i的位置
// 所以想要递归检验被更改了位置的那个孩子位置
// 就把i设为min去检验
MinHeapFixDown(A,min,n);
}
public static void HeapSort(int[] A) {
// 先堆化
MinHeap(A);
// 按序输出元素
for (int i = A.length - 1; i > 0; i--) {
swap(A,0,i);
// 缩小堆的范围(0 ~ i - 1),对对顶元素进行向下调整
MinHeapFixDown(A,0,i);
}
}
第二种易读代码
// 构建最小堆
public static void NewMinHeap(int[] A) {
int n = A.length;
for (int i = n / 2 - 1; i >= 0; i--) {
NewMinHeapFixDown(A, i, n);
}
}
// 递归地调整以i为根的子树,使其成为最小堆
public static void NewMinHeapFixDown(int[] A, int i, int n) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int min = i;
if (left < n && A[left] < A[min]) {
min = left;
}
if (right < n && A[right] < A[min]) {
min = right;
}
if (min != i) {
swap(A, i, min);
NewMinHeapFixDown(A, min, n);
}
}
// 堆排序算法
public static void NewMinHeapSort(int[] A) {
// 先构建最小堆
NewMinHeap(A);
// 逐个将堆顶元素(最小值)与堆尾元素交换,并重新调整剩余堆
for (int i = A.length - 1; i > 0; i--) {
swap(A, 0, i); // 将堆顶元素(最小值)与堆尾元素交换
NewMinHeapFixDown(A, 0, i); // 重新调整剩余元素为最小堆
}
}
大顶堆
排完是升序数组
先堆化,再将顶端数与末位数交换,再堆化,再交换……每次缩小堆化范围
堆化:
从最后一个子树开始(i = A.length/2 - 1)从后往前(从上往下)检验调整,调整成大根堆
代码方面与小顶堆比,只更改了if判断的“<”变“>”
// 构建最大堆
public static void MaxHeap(int[] A) {
int n = A.length;
for (int i = n / 2 - 1; i >= 0; i--) {
MaxHeapFixDown(A, i, n);
}
}
// 递归地调整以i为根的子树,使其成为最大堆
public static void MaxHeapFixDown(int[] A, int i, int n) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int max = i;
if (left < n && A[left] > A[max]) {
max = left;
}
if (right < n && A[right] > A[max]) {
max = right;
}
if (max != i) {
swap(A, i, max);
MaxHeapFixDown(A, max, n);
}
}
// 堆排序算法
public static void MaxHeapSort(int[] A) {
// 先构建最大堆
MaxHeap(A);
// 逐个将堆顶元素(最大值)与堆尾元素交换,并重新调整剩余堆
for (int i = A.length - 1; i > 0; i--) {
swap(A, 0, i); // 将堆顶元素(最大值)与堆尾元素交换
MaxHeapFixDown(A, 0, i); // 重新调整剩余元素为最大堆
}
}
计数排序
时间复杂度O(n)
空间复杂度O(n)
优点:快,比快排和分治快
缺点:如果数据范围大,数据稀疏,会导致辅助空间大且稀疏,造成空间浪费
重复元素解决:
在helper中对应位置计数,有几个重复元素就累计几。
更新原数组的时候,每个位置要抽光才能结束抽下一个,所有用while
负数解决:
元素本身不再对应下标,而是元素本身经过移位后对应下标
public static void countSort(int[] A) {
int max = getMaxAndMin(A)[0];
int min = getMaxAndMin(A)[1];
// 如果没有负数,就正常记,如果有,就移位
if (min > 0) {
min = 0;
}
// 因为我们需要索引getMax(A),初始化getMax(A) + 1最大索引才是getMax(A)
// 元素移位,对应辅助空间的大小也要移位扩张
int[] helper = new int[max + 1 - min];
// 计数
for (int elem : A) {
// 用+= 1是因为每有一个下标为elem的数,计数都+1
helper[elem - min] += 1;
}
// 更新原数组
int index = 0;
for (int i = 0; i < helper.length; i++) {
// 把i位置所有的数都更新到A中
while (helper[i] != 0) {
A[index++] = i + min;
helper[i]--;
}
}
}
桶排序
时间复杂度O(n) ~ O(nlgn)
如果元素分布均匀(即:每个元素一个桶),且桶的数量=元素数量。时间复杂度O(n)
如果分布很不均匀,则时间复杂度O(nlgn)
网站是一个算法可视化的网站Data Structure Visualization (usfca.edu)
1每个桶的容量是事先不知道的,所以不能用静态容器(数组),用动态容器(链表)
2将n个元素分配到各个桶中:取出一个元素的值,通过一定式子运算,得到对应的桶下标,放到这个桶里,如此反复
3放元素的时候,要和桶内原有元素比较,按次序插入
(和计数排序不同的是。计数排序记录的是数量,桶排序记录的是真正的元素)
4全部放完后,把各个桶内的元素,按桶的次序以及桶内的顺序放回原数组
基数排序
数据分布不均匀时间复杂度会变高
待排数有0和负数时,
如果只有两位数,就排两次,有三位数,就排三次,有n位数,就排n次
每次排序都用相同的那一些桶,中途别换
算法用数据结构中的表(ArrayList)实现
// 桶编号0~9
static ArrayList[] bucket = new ArrayList[10];
// 初始化桶,
static {
for (int i = 0; i < bucket.length; i++) {
// 每个桶的内容用一个ArrayList存
bucket[i] = new ArrayList<>();
}
}
//处理负数和0
public static int dealNums(int[] arr) {
int oldMin = getMaxAndMin(arr)[1];
// 如果最小值是负数就有负数,进行处理
// 如果最小值是0,就说明存在0且没负数
// 将所有数向右移一位,使算法能使用
// 没负数没0,就将min置为0,相当于没处理
if (oldMin == 0) {
oldMin = 1;
} else if (oldMin > 0) {
oldMin = 0;
}
oldMin--;
for (int i = 0; i < arr.length; i++) {
arr[i] -= oldMin;
}
// 后面恢复移位时要用这个
return oldMin;
}
public static void sort(int[] arr) {
int oldMin = dealNums(arr);
// 入桶依据的位:d
// 初始化为1:首先让个位入桶
int d = 1;
// 找出最大值,用于确定最多有几位数字,也就是循环多少遍
int max = getMaxAndMin(arr)[0];
// 找出最大值的位数
int dNum = 1;
while (max / 10 != 0) {
dNum++;
// 更新max
max /= 10;
}
// 开始循环按位排序
while (d <= dNum) {
// 函数重载
sort(arr, d++);
}
// 恢复数组(将处理前的移位再移回去)
for (int i = 0; i < arr.length; i++) {
arr[i] += oldMin;
}
}
// 将数组按位分配和收集
public static void sort(int[] arr, int d) {
// 一个个入桶
for (int i = 0; i < arr.length; i++) {
putInBucket(arr[i], getDigitData(arr[i], d));
}
//按顺序抽出(收集)
// 初始化原数组的索引
int index = 0;
// 循环每个桶
for (int i = 0; i < bucket.length; i++) {
// 取出桶里的元素
for (Object elem : bucket[i]) {
arr[index++] = (int) elem;
}
}
// 清空桶,释放空间
for (ArrayList b : bucket) {
b.clear();
}
}
// 入桶
public static void putInBucket(int data, int digitData) {
switch (digitData) {
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;
}
}