文中用到的一个交换方法
/**
* 2020.8.7被面试评价为不懂排序算法,但我自我感觉对排序算法有一定的了解,所以打算凭自己对排序算法的理解手写一下排序算法
* 2020.8.8 在写一些排序算法时,遇到了很多错误,不得不承认,面试官的评价很中肯,没手写过代码前,不要说自己学过这个代码
* 学习排序算法时手写的代码,结合自己的理解
* 每个排序的测试方法都在它之前的test方法中
* @author 不放泣
*/
/**
* 用于交换一个数组中两个指定下标的数值的方法
*
* @param nums 传入的数组
* @param a 待交换的值的下标
* @param b 待交换的值的下标
*/
void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
选择排序
直接选择排序
@Test
public void testSelectSort() {
int[] nums = {3, 2, 3, 3, 5, 6, 9, 7, 8, 55, 99, 123, 22, 1};
System.out.println(Arrays.toString(selectSort(nums)));
}
/**
* 选择排序
* 每次找到剩余数据中的最小值
*
* @param nums
*/
public int[] selectSort(int[] nums) {
int length = nums.length;
int minIndex;
for (int i = 0; i < length; i++) {
minIndex = i;
for (int j = i; j < length; j++) {
if (nums[minIndex] > nums[j]) {
nums[minIndex] = nums[j];
minIndex = j;
}
}
swap(nums, i, minIndex);
}
return nums;
}
堆排序
@Test
public void testHeapSort() {
int[] nums = {3, 2, 3, 3, 5, 6, 9, 7, 8, 55, 99, 123, 22, 1, 23, 6, 2, 94, 555, 61, 6161, 64, 9, 5, 1, 284, 54, 46, 4, 787,};
ArrayList<Long> time = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
heapSort(nums, time);
// System.out.println(Arrays.toString(heapSort(nums, time)));
}
System.out.println(Arrays.toString(heapSort(nums, time)));
Long avg = 0L;
for (int i = 0; i < 100; i++) {
avg += time.get(i);
}
System.out.println(avg / 100);
}
/**
* 堆排序
* 堆排序是选择排序的一种,
* 它将数组以一种大顶堆或者小顶堆的数据结构排序,然后将最大(或者最小)数据从这个堆中依次取出,然后调整堆,使其仍符合大顶堆或者小顶堆的标准
* 大顶堆是一种二叉完全树,且父节点的数值大于子节点
* 用数组来模拟堆的数据结构时,若堆的根节点是从nums[0]开始的,则若父节点的下标为i,子节点的下标为2*i+1与2*i+2,
* 若子节点的下标为j,则其父节点的下标为(j+1)/2-1
* 所有下标为偶数的节点的兄弟节点为其节点下标-1,所有下标为奇数的节点的兄弟节点为其节点下标+1
* 在定好父子节点之间的关系后就需要调整所有父子节点间的对应关系了
*
* @param nums
* @return
*/
public int[] heapSort(int[] nums, List<Long> time) {
int length = nums.length;
long start = System.nanoTime();
for (int i = 0; i < length - 1; i++) {
adjustHeap(nums, 0, length - i - 1);
swap(nums, 0, length - i - 1);
}
time.add(System.nanoTime() - start);
return nums;
}
public void adjustHeap(int[] nums, int begin, int end) {
/*
for (int i = (end + 1) / 2 - 1; i >= begin; i--) {
if (i * 2 + 2 <= end && nums[i * 2 + 1] < nums[i * 2 + 2]) {
if (nums[i] < nums[i * 2 + 2]) {
swap(nums, i, 2 * i + 2);
}
} else {
if (nums[i] < nums[i * 2 + 1]) {
swap(nums, i, 2 * i + 1);
}
}
}
*/
//最后一个节点的下标为end所以其父节点的下标为(end+1)/2-1,所以(end+1)/2-1+1节点不存在子节点
//对每个父节点进行调整
//但如果每次取出堆顶元素后都对所有父节点进行扫描,有点浪费资源
//所以又写了另一部分,但其实一直调用初始化堆的代码也能达到效果
//对于实现了大顶堆的算法平均所需的时间为20251ns
//对于没实现的算法平均时间为35127ns(代码在上面的注释中)
//每次测试都是取100次中的平均值
//改进了代码 替换了原方法中的swap方法,时间减少了一半左右
//14958ns,18254ns,9085ns,20950ns 花费时间的波动很大
if (end != nums.length - 1) {
adjustUnit(nums, begin, end);
} else {
//初始化大顶堆
for (int i = (end + 1) / 2 - 1; i >= begin; i--) {
adjustUnit(nums, i, end);
}
}
}
public void adjustUnit(int[] nums, int begin, int end) {
int i = begin;
int key = nums[i];
int j;
while (i * 2 + 1 <= end) {
j = i * 2 + 1;
if (j + 1 <= end && nums[j] < nums[j + 1]) {
j++;
}
if (nums[i] < nums[j]) {
nums[i] = nums[j];
nums[j] = key;
i = j;
} else {
break;
}
}
}
插入排序
直接插入排序
@Test
public void testInsertSort() {
int[] nums = {3, 2, 3, 3, 5, 6, 9, 7, 8, 55, 99, 123, 22, 1};
System.out.println(Arrays.toString(insertSort(nums)));
}
/**
* 插入排序
* 在此插入排序中,使用了一种native方法,system.arraycopy,这个方法能将数组的一部分复制到(另一个)数组的某个位置
* 在arraylist中的插入某个指定下标的数组时,也使用了此方法,应该比将数组中插入点后的所有数据手动的后移一位的效率高
*
* @param nums
* @return
*/
public int[] insertSort(int[] nums) {
//此处不确定,个人感觉将length提出来后不用调用nums[]中的length可能可以提高效率
int length = nums.length;
int temp;
for (int i = 1; i < length; i++) {
temp = nums[i];
for (int j = i - 1; j >= 0; j--) {
//分了两种情况讨论,一种是找到前面的数组中比temp值低时,另一种是没找到比temp值还要低的值,就把temp值放入第一个位置
//也看到过另一种实现方式,每次temp跟前面的值比较大小后,如果前面的值大于temp中的值就把两个值交换位置,这样的话就可以不需要分类讨论
if (temp >= nums[j]) {
System.arraycopy(nums, j + 1, nums, j + 2, i - j - 1);
nums[j + 1] = temp;
break;
} else if (j == 0) {
System.arraycopy(nums, 0, nums, 1, i);
nums[0] = temp;
break;
}
}
}
return nums;
}
希尔排序
@Test
public void testShellSort(){
int[] nums = {3, 2, 3, 3, 5, 6, 9, 7, 8, 55, 99, 123, 22, 1, 23, 6, 2, 94, 131313,555, 61, 6161, 64, 9, 5, 1, 284, 54, 46, 4, 787, 122,64646};
shellSort(nums);
System.out.println(Arrays.toString(nums));
}
/**
* 希尔排序
* 这是一种加强版的插入排序,若将shellInsert中的d参数设为1,则与直接插入排序一样
* 直接插入排序适用于数组较为有序的情况,而希尔排序在d!=1时就在构造这种较为有序的情况
* @param nums
* @return
*/
public int[] shellSort(int[] nums){
int d=nums.length/2;
while (d>=1){
shellInsert(nums,d);
d/=2;
}
return nums;
}
public void shellInsert(int[] nums, int d) {
int temp;
for (int i=d;i<nums.length;i++){
temp=nums[i];
int j=i;
for(;j>=d;){
if(nums[j-d]>temp){
nums[j]=nums[j-d];
j-=d;
}else {
break;
}
}
nums[j]=temp;
}
}
交换排序
冒泡排序
@Test
public void testBubbleSort() {
int[] nums = {3, 2, 3, 3, 5, 6, 9, 7, 8, 55, 99, 123, 22, 1};
System.out.println(Arrays.toString(bubbleSort(nums)));
}
/**
* 冒泡排序
* 此排序方法属于交换排序
* 此排序有两层嵌套for循环,每一轮内循环都会使nums[length-1-i]比它前面的数字大
* 在内循环中所执行的为若相邻的元素中左边的大于右边的元素则这两个元素互换位置
*
* @param nums
* @return
*/
public int[] bubbleSort(int[] nums) {
int length = nums.length;
for (int i = 0; i < length - 1; i++) {
for (int j = 0; j < length - i - 1; j++) {
if (nums[j] > nums[j + 1]) {
swap(nums, j, j + 1);
}
}
}
return nums;
}
快速排序
@Test
public void testQuickSort() {
int[] nums = {3, 2, 3, 3, 5, 6, 9, 7, 8, 55, 99, 123, 22, 1};
quickSort(nums, 0, nums.length - 1);
System.out.println(Arrays.toString(nums));
}
/**
* 快速排序
* 此排序方法属于交换排序
* 此排序首先要确定一个标准,将整个数组分为比这个标准小的部分与比这个标准大的部分
* 在本方法中取end下标对应的数字为这个标准
*
* @param nums 需要排序的数组
* @param begin 需要排序的数组的起始下标
* @param end 需要排序的数组的结尾下标
* @return
*/
public void quickSort(int[] nums, int begin, int end) {
if (begin >= end) {
return;
}
int leftIndex = begin;
int rightIndex = end - 1;
while (leftIndex < rightIndex) {
if (nums[leftIndex] <= nums[end]) {
leftIndex++;
} else if (nums[rightIndex] >= nums[end]) {
rightIndex--;
} else {
swap(nums, leftIndex, rightIndex);
}
}
if (rightIndex != end - 1) {
swap(nums, rightIndex, end);
}
quickSort(nums, leftIndex, rightIndex - 1);
quickSort(nums, rightIndex + 1, end);
}
归并排序
@Test
public void testMergeSort() {
int[] nums = {3, 2, 3, 3, 5, 6, 9, 7, 8, 55, 99, 123, 22, 1, 23, 6, 2, 94, 555, 61, 6161, 64, 9, 5, 1, 284, 54, 46, 4, 787, 122};
mergeSort(nums,0,nums.length-1);
System.out.println(Arrays.toString(nums));
}
/**
* 归并算法
* 这是个先将数组打散,对2个数字进行排序,在对数字进行合并,再对两个有序数组进行排序的算法
* 对于这种不知道要调用多少次的函数,一般用递归来解决,省代码,但费脑子
*
* @param nums
* @return
*/
public void mergeSort(int[] nums, int begin, int end) {
if (begin >= end) {
return;
}
int mid = (begin + end) / 2;
mergeSort(nums, begin, mid);
mergeSort(nums, mid + 1, end);
merge(nums, begin, mid, end);
}
/**
* 在第一次执行这个方法时,
* 只有当 mergeSort(nums, begin, mid);mergeSort(nums, mid + 1, end);
* 这两个方法都直接return时才会执行merge方法,
* 因为begin<end时才会不return,所以传入此方法的第一次参数可能为:end-begin+1=1,end-begin+1=2
* 为1时begin=end,为2时begin=mid,mid+1=end
* 这是此排序所分成的最小单位
* @param nums
* @param begin
* @param mid
* @param end
*/
public void merge(int[] nums, int begin, int mid, int end) {
int i = begin, j = mid + 1;
int[] temp = new int[end - begin+1];
int index = 0;
while (i <= mid || j <= end) {
if (i <= mid && j <= end) {
if (nums[i] < nums[j]) {
temp[index++] = nums[i++];
} else {
temp[index++] = nums[j++];
}
} else if (i > mid) {
temp[index++] = nums[j++];
} else {
temp[index++] = nums[i++];
}
}
System.arraycopy(temp,0,nums,begin,end-begin+1);
}
计数排序
@Test
public void testCountSort(){
int[] nums = {3, 2, 3, 3, 5, 6, 9, 7, 8, 55, 99, 123, 22, 1, 23, 6, 2, 94, 555, 61, 6161, 64, 9, 5, 1, 284, 54, 46, 4, 787, 122,64646};
countSort(nums);
System.out.println(Arrays.toString(nums));
}
/**
* 计数排序
* 对于整数,且有多个整数存在重复的情况时,此排序算法效率较高
* 大致分为3部分
* 1.找到最大值
* 2.创建以此最大值为长度的数组,下标i对应的nums[i]值为i的个数
* 3.将此数组中的值按下标值大小输出给nums
* 对下标进行一些映射后,可以用下标表示负数以及小数
* @param nums
* @return
*/
public int[] countSort(int[] nums){
int max=nums[0];
for (int num : nums) {
if (max < num) {
max = num;
}
}
int[] temp=new int[max+1];
for (int num : nums) {
temp[num]++;
}
int index=0;
for (int i=0;i<temp.length;i++){
for(int j=0;j<temp[i];j++){
nums[index++]=i;
}
}
return nums;
}