1.冒泡排序
冒泡排序的思想十分简单,如果左边的数字大于右边的数字,二者进行交换,每一次都会将当前未排序的数据中最大值移动到数组的最右端。
public static void bubbleSort(int[] nums){
for(int i = 0; i < nums.length;i++){
for(int j = 0; j < nums.length-1-i;j++){
if(nums[j] > nums[j+1]){
int temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
}
如果此时数组已经有序,上边的算法依然会一直进行比较,我们可以使用一个标记来检查当前数组元素是否已经有序,若有序,则结束程序。
public static void bubbleSort(int[] nums){
boolean flag = false;
for(int i = 0; i < nums.length && !flag;i++){
flag = true;
for(int j = 0; j < nums.length-1-i;j++){
if(nums[j] > nums[j+1]){
flag = false;
int temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
System.out.println(count);
}
2.选择排序
选择排序的思想则是,首先找到最大的元素,移动到数组的末尾,然后在剩下的n-1的元素中找到最大的元素,将其移动到数组的倒数第二个位置,重复这个过程,直到剩下一个元素。
public static void selectSort(int[] nums){
int len = nums.length;
for(int i = 0; i < nums.length;i++){
int maxIndex = findMax(nums,i);
int temp = nums[maxIndex];
nums[maxIndex] = nums[len-1-i];
nums[len-1-i] = temp;
}
}
public static int findMax(int[] nums, int times){
int res = 0;
for(int i = 1; i < nums.length-times;i++){
if(nums[res] < nums[i]){
res = i;
}
}
return res;
}
同样的,如果数组已经有序,上边的代码依然会去遍历数组,找最大值,我们依然可以使用一个标记位来记录当前数组是否已经有序,如果有序,程序结束。
public static void selectSort(int[] nums){
int len = nums.length;
boolean isSorted = false;
for(int i = 0; i < nums.length && !isSorted;i++){
int maxIndex = nums.length - i - 1;
isSorted = true;
//找最大值的下标
for(int j = maxIndex-1; j >= 0;j--){
if(nums[maxIndex] < nums[j]){
maxIndex = j;
isSorted = false;
}
}
int temp = nums[maxIndex];
nums[maxIndex] = nums[len-1-i];
nums[len-1-i] = temp;
}
}
3.快速排序
快速排序的思想是,选中一个基准,把小于当前基准位置数据的数据移动到基准索引的左边,大于当前基准位置数据的数据移动到基准索引的右边,之后在对基准左边使用这个算法,对基准右边使用这个算法。
public static void quickSort(int[] nums,int start, int end){
//只有一个数据
if(start >= end){
return;
}
int midlle = partation(nums,start,end);
quickSort(nums,start,midlle-1);
quickSort(nums,midlle+1,end);
}
public static int partation(int[] nums, int start,int end){
int pivot = nums[start];
int left = start+1;
int right = end;
while(left < right){
//找打第一个大于基准的数字
while(left < right && nums[left] <= pivot){
left++;
}
//找到第一个小于基准的数字
while(left < right && nums[right] >= pivot){
right--;
}
//交换两者位置
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
//如果left 和 right 相等,单独比较 nums[right]和pivot比较
if(left == right && nums[right] > pivot) right--;
//交换两者位置
int temp = nums[right];
nums[right] = pivot;
nums[start] = temp;
return right;
}
4.插入排序
插入排序的思想很简单,将数组分为已经排好序的一部分和未排序的一部分,排序算法开始的时候,我们认为第1个元素已经排好序,从后往前判断未排好序第一个元素应该插入到哪里。
public static void insertSort(int[] nums){
//默认第一个数字已经排好序
int sorted = 1;
for (int i = 1; i < nums.length; i++) {
int value = nums[i];
int position = i;
//从后往前,判断当前元素应该插入到哪里
while(position > 0 && nums[position-1] > value){
nums[position] = nums[position-1];
position--;
}
nums[position] = value;
}
}
5.归并排序
通常情况下使用大顶堆,对堆进行层序遍历,得到升序排序。
public static void heapSort(int[] arr) {
// 构建初始大顶堆
buildMaxHeap(arr);
for (int i = arr.length - 1; i > 0; i--) {
// 将最大值交换到数组最后
swap(arr, 0, i);
// 调整剩余数组,使其满足大顶堆
maxHeapify(arr, 0, i);
}
}
/* // 构建初始大顶堆
private static void buildMaxHeap(int[] arr) {
// 从最后一个非叶子结点开始调整大顶堆,最后一个非叶子结点的下标就是 arr.length / 2-1
for (int i = arr.length / 2 - 1; i >= 0; i--) {
maxHeapify(arr, i, arr.length);
}
}
// 调整大顶堆,第三个参数表示剩余未排序的数字的数量,也就是剩余堆的大小
private static void maxHeapify(int[] arr, int i, int heapSize) {
// 左子结点下标
int l = 2 * i + 1;
// 右子结点下标
int r = l + 1;
// 记录根结点、左子树结点、右子树结点三者中的最大值下标
int largest = i;
// 与左子树结点比较
if (l < heapSize && arr[l] > arr[largest]) {
largest = l;
}
// 与右子树结点比较
if (r < heapSize && arr[r] > arr[largest]) {
largest = r;
}
if (largest != i) {
// 将最大值交换为根结点
swap(arr, i, largest);
// 再次调整交换数字后的大顶堆
maxHeapify(arr, largest, heapSize);
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}*/
6.希尔排序
希尔排序是对插入排序的优化,当数组部分有序的时候,插入排序的效率较高,因此我们构建相对有序的分段数组,在每一段使用插入排序,不断缩小小分组的大小,直到小分组大小为1.
public static void shellSort(int[] nums){
int len = nums.length;
//分组不停的缩小
for(int gap = len / 2; gap > 0; gap /= 2){
//获取每个分组中的元素
for (int startIndex = 0; startIndex < gap; startIndex++) {
//开始插入排序
for (int currentIndex = startIndex + gap; currentIndex < len; currentIndex+= gap) {
int curNumber = nums[currentIndex];
int preIndex = currentIndex - gap;
while(preIndex >= startIndex && nums[preIndex] > curNumber){
nums[preIndex + gap] = nums[preIndex];
preIndex -= gap;
}
nums[preIndex+gap] = curNumber;
}
}
}
}
7.计数排序
计数排序就是记录对应元素出现的个数,统计比当前元素小的元素个数,就知道了当前元素在排好序数组中的位置。
public static void countingSort(int[] nums){
// 判空及防止数组越界
if (nums == null || nums.length <= 1) return;
//找到最大值和最小值
int max = nums[0];
int min = nums[0];
for (int i = 1; i < nums.length; i++) {
max = Math.max(max,nums[i]);
min = Math.min(min,nums[i]);
}
int len = max - min + 1;
int[] counting = new int[len];
//开始统计个数
for (int i = 0; i < nums.length; i++) {
counting[nums[i] - min]++;
}
//记录前边比自己小的元素个数
int preMin = 0;
for (int i = 0; i < len; i++) {
preMin += counting[i];
counting[i] = preMin - counting[i];
}
//开始排序
int[] result = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
result[counting[nums[i] - min]] = nums[i];
counting[nums[i] - min]--;
}
//结果赋回nums
for (int i = 0; i < nums.length; i++) {
nums[i] = result[i];
}
}
8.基数排序
基数排序的思想是,我们准备0-9 10个bucket,先按照个位数字放入桶中,取出来,再按照十位数字放入桶中,重复这个过程,直到最大元素的最高位放完,就得到了排好序的数组。(每一轮排序采用计数排序,采用的是倒序的计数排序,直接计算当前元素在排好序数组中的位置)。,第一个算法是在待排序元素中没有负数的情况,若排序元素中有负数,我们可以将桶设置为-9 - 9 19个桶(0-18 给对应的元素+9即可获得当前位置)。
public static void radixSort(int[] nums){
if (nums == null || nums.length == 1) {
return;
}
//找到最大值
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
max = Math.max(max, nums[i]);
}
//得到最大值的位数
int maxLength = 0;
while(max > 0){
maxLength++;
max /= 10;
}
//开始基数排序
int[] counting = new int[10];
int[] result = new int[nums.length];
int dev = 1;
for (int i = 0; i < maxLength; i++) {
//根据当前位数 统计基数数量
for(int value : nums){
int radix = value/dev % 10;
counting[radix]++;
}
//采用逆序的计数排序,统计当前元素在排好序元素什么位置
for (int j = 1; j < counting.length; j++) {
counting[j] += counting[j-1];
}
//开始基数排序
for (int j = nums.length-1; j >= 0 ; j--) {
int radix = nums[j] / dev % 10;
result[--counting[radix]] = nums[j];
}
// 计数排序完成后,将结果拷贝回 arr 数组
System.arraycopy(result, 0, nums, 0, nums.length);
// 将计数数组重置为 0
Arrays.fill(counting, 0);
dev *= 10;
}
}
public class RadixSort {
public static void radixSort(int[] arr) {
if (arr == null) return;
// 找出最长的数
int max = 0;
for (int value : arr) {
if (Math.abs(value) > max) {
max = Math.abs(value);
}
}
// 计算最长数字的长度
int maxDigitLength = 0;
while (max != 0) {
maxDigitLength++;
max /= 10;
}
// 使用计数排序算法对基数进行排序,下标 [0, 18] 对应基数 [-9, 9]
int[] counting = new int[19];
int[] result = new int[arr.length];
int dev = 1;
for (int i = 0; i < maxDigitLength; i++) {
for (int value : arr) {
// 下标调整
int radix = value / dev % 10 + 9;
counting[radix]++;
}
for (int j = 1; j < counting.length; j++) {
counting[j] += counting[j - 1];
}
// 使用倒序遍历的方式完成计数排序
for (int j = arr.length - 1; j >= 0; j--) {
// 下标调整
int radix = arr[j] / dev % 10 + 9;
result[--counting[radix]] = arr[j];
}
// 计数排序完成后,将结果拷贝回 arr 数组
System.arraycopy(result, 0, arr, 0, arr.length);
// 将计数数组重置为 0
Arrays.fill(counting, 0);
dev *= 10;
}
}
}
9.桶排序
桶排序的思想是:
将区间划分为 n 个相同大小的子区间,每个子区间称为一个桶
遍历数组,将每个数字装入桶中
对每个桶内的数字单独排序,这里需要采用其他排序算法,如插入、归并、快排等
最后按照顺序将所有桶内的数字合并起来
桶排序在实际工作中的应用较少,不仅因为它需要借助于其他排序算法,还因为桶排序算法基于一个假设:所有输入数据都服从均匀分布,也就是说输入数据应该尽可能地均匀分布在每个桶中。只有这个假设成立时,桶排序运行效率才比较高。
public static void bucketSort(int[] nums){
if (nums == null || nums.length == 1) {
return;
}
//获得最大值 最小值
int max = nums[0];
int min = nums[0];
for (int i = 1; i < nums.length; i++) {
max = Math.max(max,nums[i]);
min = Math.min(min,nums[i]);
}
int bucketAmount = 100;
//桶和桶之间的间距
double gap = 1.0 * (max - min)/(bucketAmount - 1);
int[][] buckets = new int[bucketAmount][];
//装桶
for(int value : nums){
int index = (int) ((value - min)/gap);
buckets[index] = add(buckets[index],value);
}
//对每一个桶单独排序
int index = 0;
for (int i = 0; i < bucketAmount; i++) {
if(buckets[i] == null || buckets[i].length == 0) continue;
insertSort(buckets[i]);
//排序后将元素收集起来
System.arraycopy(buckets[i],0,nums,index,buckets[i].length);
index += buckets[i].length;
}
}
// 数组扩容
public static int[] add(int[] arr, int num) {
if (arr == null) return new int[]{num};
int[] newArr = Arrays.copyOf(arr, arr.length + 1);
newArr[arr.length] = num;
return newArr;
}
10.堆排序
堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。
public static void heapSort(int[] arr) {
// 构建初始大顶堆
buildMaxHeap(arr);
for (int i = arr.length - 1; i > 0; i--) {
// 将最大值交换到数组最后
swap(arr, 0, i);
// 调整剩余数组,使其满足大顶堆
maxHeapify(arr, 0, i);
}
}
public static void buildMaxHeap(int[] nums){
//从最后一个非叶子节点开始调整
int len = nums.length;
for (int i = len/2-1; i >= 0; i--) {
maxHeapify(nums,i,len);
}
}
public static void maxHeapify(int[] nums,int i, int size){
//找到当前根节点的左右孩子
int left = i*2 + 1;
int right = left + 1;
//找到三者中的最大值
int large = i;
if(left < size && nums[left] > nums[large]){
large =left;
}
if(right < size && nums[right] > nums[large]){
large = right;
}
if (large != i) {
// 将最大值交换为根结点
swap(nums,i,large);
// 再次调整交换数字后的大顶堆
maxHeapify(nums,large,size);
}
}
public static void swap(int[] nums, int i, int large){
int temp = nums[i];
nums[i] = nums[large];
nums[large] = temp;
}