十大排序Java实现
程序员文章站
2022-03-25 13:49:12
...
public class Algorithm {
/**
* 遍历数组
*/
public static void traversal(int[] array) {
for(int i : array) {
System.out.print(i+" ");
}
System.out.println();
System.out.println("---------------------------------------");
}
/**
* 冒泡排序(交换类排序)
* 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
* 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
* 针对所有的元素重复以上的步骤,除了最后一个;
* 重复步骤1~3,直到排序完成。
*/
public static void bubbles(int[] array) {
//次数
for(int i = 0; i < array.length; i++) {
for(int j = 0; j < array.length - i - 1; j++) {
if(array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
System.out.println("冒泡:");
traversal(array);
}
/**
* 快速排序(交换类排序)
* 从待排序的n个记录中任意选取一个记录(通常选取第一个记录)为分区标准;
* 把所有小于该排序列的记录移动到左边,把所有大于该排序码的记录移动到右边,中间放所选记录,称之为第一趟排序;
* 然后对前后两个子序列分别重复上述过程,直到所有记录都排好序。
* @param low 最低位的下标
* @param high 最高位的下标
*/
public static void quickSort(int[] array, int start, int end) {
int low = start;
int high = end;
//以最左边的数为pivot
int pivot = array[low];
while(low < high) {
//从后往前比较, 如果没有比pivot小的数,就把后面的下标往前移
while(low < high && array[high] >= pivot) {
high--;
}
//此时如果比pivot小,则调换位置
if(array[high] < pivot) {
int temp = array[high];
array[high] = array[low];
array[low] = temp;
}
//从前往后比较,如果没有比pivot大的数,就把前面的下标往后移
while(low < high && array[low] <= pivot) {
low++;
}
//此时如果比pivot大,则调换位置
if(array[low] > pivot) {
int temp = array[low];
array[low] = array[high];
array[high] = temp;
}
//此时第一次循环结束
}
//递归调用
//pivot左边的数组
if(low > start) {
quickSort(array, start, low - 1);
}
//pivot右边的数组
if(high < end) {
quickSort(array, high + 1, end);
}
traversal(array);
}
/**
* 插入排序(插入类)
* 从待排序的n个记录中的第二个记录开始,
* 依次与前面的记录比较并寻找插入的位置,
* 每次外循环结束后,将当前的数插入到合适的位置。
*/
public static void insertSort(int[] array) {
// for(int i = 0; i < array.length - 1; i++) {
// for(int j = i + 1; j > 0; j--) {
// if(array[j] < array[j - 1]) {
// int temp = array[j];
// array[j] = array[j-1];
// array[j-1] = temp;
// }
// }
// }
for(int i = 1; i < array.length; i++) {
int preIndex = i - 1;
int current = array[i];
//与之前的数逐一比较,只要比其小就交换位置
while(preIndex >= 0 && array[preIndex] > current) {
array[preIndex + 1] = array[preIndex];
preIndex--;
}
//因为之前--,所以此时赋值索引需要+1
array[preIndex + 1] = current;
}
System.out.println("插入:");
traversal(array);
}
/**
* 希尔排序(插入类)
* Shell排序法是对相邻指定距离(称为增量)的元素进行比较,并不断把增量缩小至1,完成排序。
* 先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
* 按增量序列个数k,对序列进行k 趟排序;
* 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。
* 仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
*
* @param d 定义的初始增量
*/
public static void shellSort(int[] array, int d) {
//循环的次数为增量缩小到1的次数
for(int i = d; i > 0; i = i/2) {
for(int j = 0; j < i; j++) {
//进行插入排序
for(int k = j; k < array.length - i; k = k + i) {
//比较当前为k的索引所表示的数和后面这一趟需要比较的所有元素(例如0和0,2,4,6,8分别比较)
int temp = array[k+i];
int currentIndex = k;
while(currentIndex >= 0 && temp < array[currentIndex]) {
array[currentIndex+i] = array[currentIndex];
currentIndex = currentIndex - i;
}
array[currentIndex+i] = temp;
}
}
}
System.out.println("希尔:");
traversal(array);
}
/**
* 选择排序(选择类)
* 首先在未排序序列中找到最小(大)元素,
* 存放到排序序列的起始位置,
* 然后,再从剩余未排序元素中继续寻找最小(大)元素,
* 然后放到已排序序列的末尾。
* 以此类推,直到所有元素均排序完毕。
*/
public static void selectSort(int[] array) {
//假设最小数的索引为0
int minIndex = 0;
//选择的次数
for(int i = 0; i < array.length; i++) {
minIndex = i;
for(int j = i + 1; j < array.length; j++) {
if(array[j] < array[minIndex]) {
minIndex = j;
}
}
//交换当前索引数据和最小数索引的数据
int temp = array[i];
array[i] = array[minIndex];
array[minIndex] = temp;
}
System.out.println("选择:");
traversal(array);
}
/**
* 堆排序(选择类)
* 堆的性质:
* (1)性质:完全二叉树或者是近似完全二叉树;
* (2)分类:大顶堆:父节点不小于子节点键值,小顶堆:父节点不大于子节点键值。
* (3)左右孩子:没有大小的顺序。
* (4)堆的存储
* 一般都用数组来存储堆,i结点的父结点下标就为(i–1)/2。
* 它的左右子结点下标分别为 2∗i+1 和 2∗i+2。如第0个结点左右子结点下标分别为1和2。
* (5)堆的操作
* 建立:
* 以最小堆为例,
* 如果以数组存储元素时,一个数组具有对应的树表示形式,
* 但树并不满足堆的条件,需要重新排列元素,可以建立“堆化”的树。
* 插入:
* 将一个新元素插入到表尾,即数组末尾时,
* 如果新构成的二叉树不满足堆的性质,需要重新排列元素。
* 删除:
* 堆排序中,删除一个元素总是发生在堆顶,因为堆顶的元素是最小的(小顶堆中)。
* 表中最后一个元素用来填补空缺位置,结果树被更新以满足堆条件。
*/
public static void heapSort(int[] array) {
//构建大顶堆
array = buildMaxHeap(array);
//此时,最大的数在最顶上,每个结点及其子节点为近似二叉树
//但是左右子树的大小顺序不一定正确
//需要把顶上节点的数和最后一个节点交换,保证最后一个数最大,
//然后重新调整剩余的数,使第二大的数到顶上的节点,以此类推。。。
for(int i = array.length - 1; i >= 1; i--) {
//交换最后一个元素和堆顶元素
int temp = array[i];
array[i] = array[0];
array[0] = temp;
//重新调整树形结构(只需要调整堆顶的即可,因为其他节点结构没变,数组长度去掉已经排好的节点)
adjustDownToUp(array, 0, i);
}
System.out.println("堆:");
traversal(array);
}
/**
* 构建大顶堆
*/
public static int[] buildMaxHeap(int[] arr) {
//从最后一个节点arr.length-1的父节点(arr.length-1 - 1)/2开始,直达根节点0,反复调整堆
for(int i = (arr.length-2)/2; i >= 0; i--) {
adjustDownToUp(arr, i, arr.length);
}
return arr;
}
/**
* 将元素array[k]自下往上逐步调整树形结构
* @param array 需要堆排序的数组
* @param k 需要排序的数组索引
* @param length 数组长度
*/
public static void adjustDownToUp(int[] array, int k, int length) {
//保存当前节点的值
int temp = array[k];
//i为k的左子节点,每次增量为其的左子节点
for(int i=2*k+1; i<=length-1; i=2*i+1) {
//如果只有左子节点,那么length=i+1
//如果有右子节点,那么length=i+2,即i+1<length(才能进行左右子节点大小的比较)
if(i+1<length && array[i] < array[i+1]) {
//存在右子节点,并且右子节点比较大
//把需要比较的节点索引+1
i++;
}
//比较父节点和子节点
//每次比较成功只会把子节点的值覆盖给父节点,而父节点的值理应和子节点交换,
//所以每次比较比较temp和array[i]而不是array[k]和array[i]
if(temp < array[i]) {
//如果父节点小于子节点,交换子节点到父节点上去
array[k] = array[i];
//重新给k赋值,修改下标,便于向下的比较
k = i;
}else {
//如果父节点大于等于子节点,不需要交换,
//因为没有交换,所以也不用向下交换,循环结束
break;
}
}
//此时k为其子节点或者子节点的子节点的...下标
array[k] = temp;
}
/**
* 删除堆顶元素
*/
public static void deleteMaxHeap(int[] array) {
//将堆的最后一个元素与堆顶元素交换,堆底元素设为-99999
array[0] = array[array.length - 1];
array[array.length - 1] = -99999;
//对此时的根节点进行向下调整
adjustDownToUp(array, 0, array.length);
System.out.println("删除堆顶:");
traversal(array);
}
/**
* 对大顶堆插入一个新数据:
* 先将新节点放在堆的末端,再对这个新节点执行向上调整操作。
* 假设数组的最后一个元素array[array.length-1]为空,新插入的结点初始时放置在此处。
*/
public static void insertHeap(int[] array, int newData) {
//将新加入的节点放置在最后一个节点位置上(假设最后一个元素为空)
array[array.length - 1] = newData;
//将新插入的节点向上比较
//需要比较的节点
int k = array.length - 1;
//父节点
int parent = (array.length - 2)/2;
while(parent >=0 && newData > array[parent]) {
//父节点下移
array[k] = array[parent];
k = parent;
if(parent != 0) {
parent = (parent-1)/2;
}else {
break;
}
}
array[k] = newData;
}
/**
* 归并排序:
* 把长度为n的输入序列分成两个长度为n/2的子序列;
* 对这两个子序列分别采用归并排序;
* 将两个排序好的子序列合并成一个最终的排序序列。
*/
public static void mergeSort(int[] array, int leftIndex, int rightIndex) {
if(leftIndex < rightIndex) {
int mid = (leftIndex + rightIndex)/2;
mergeSort(array, leftIndex, mid);
mergeSort(array, mid + 1, rightIndex);
//左右归并
merge(array, leftIndex, mid, rightIndex);
}
System.out.println("归并:");
traversal(array);
}
/**
* 将两个有序数列并为一个有序数列
*/
public static void merge(int[] array, int leftIndex, int midIndex, int rightIndex) {
//定义一个临时数组,用来存储排序后的结果
int[] arr = new int[rightIndex - leftIndex + 1];
//第一个有序数列的起始索引
int sortedIndex1 = leftIndex;
//第二个有序数列的起始索引
int sortedIndex2 = midIndex + 1;
//临时数组的索引
int tempIndex = 0;
//当两个有序数列遍历都不越界时
while(sortedIndex1 <= midIndex && sortedIndex2 <= rightIndex) {
//比较两边的数组的大小
if(array[sortedIndex1] > array[sortedIndex2]) {
//较小的放入临时数组中
arr[tempIndex] = array[sortedIndex2];
sortedIndex2++;
}else {
arr[tempIndex] = array[sortedIndex1];
sortedIndex1++;
}
tempIndex++;
}
//将剩下的一个有序数列放入临时数组中
while(sortedIndex1 <= midIndex) {
//第一个有序数列剩余
arr[tempIndex] = array[sortedIndex1];
tempIndex++;
sortedIndex1++;
}
while(sortedIndex2 <= rightIndex) {
//第二个有序数列剩余
arr[tempIndex] = array[sortedIndex2];
tempIndex++;
sortedIndex2++;
}
//将临时数组的数覆盖原来的数组
for(int i = 0; i < arr.length; i++) {
array[leftIndex + i] = arr[i];
}
}
/**
* 计数排序(线性时间)
* 基本思想是对于给定的输入序列中的每一个元素x,
* 确定该序列中值小于x的元素的个数。
* 一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。
* 例如,如果输入序列中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。
* 当然,如果有多个元素具有相同的值时,我们不能将这些元素放在输出序列的同一个位置上,在代码中作适当的修改即可。
*
* 算法步骤:
* (1)找出待排序的数组中最大的元素;
* (2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
* (3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
* (4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
*
* @param array 待排序数组
* @param maxValue 待排序数的最大值
*/
public static void countSort(int[] array, int maxValue) {
//待排序的数组的索引
int index = 0;
//加上0,数组长度为maxValue+1
int[] bucket = new int[maxValue + 1];
//将待排序的数组遍历,与值相等的bucket数组的索引进行计数
for(int i = 0; i < array.length; i++) {
bucket[array[i]]++;
}
for(int i = 0; i < bucket.length; i++) {
while(bucket[i] > 0) {
//说明该处索引对应的相等的值存在
array[index] = i;
bucket[i]--;
index++;
}
}
System.out.println("计数:");
traversal(array);
}
/**
* 桶排序:(线性时间)
* 桶排序算法想法类似于散列表。
* 首先要假设待排序的元素输入符合某种均匀分布,
* 例如数据均匀分布在[ 0,1)区间上,则可将此区间划分为10个小区间,称为桶,
* 对散布到同一个桶中的元素再排序。
*
* 排序过程:
* (1)设置一个定量的数组当作空桶子;
* (2)寻访序列,并且把记录一个一个放到对应的桶子去;
* (3)对每个不是空的桶子进行排序。
* (4)从不是空的桶子里把项目再放回原来的序列中。
*
* @param array 待排序数组
* @param bucketSize 桶的数量
*/
public static void bucketSort(int[] array, int bucketSize) {
//创建桶的数组
List<LinkedList<Integer>> list = new ArrayList<>();
for(int i = 0; i < bucketSize; i++) {
LinkedList<Integer> bucket = new LinkedList<>();
list.add(bucket);
}
//将待排序数组分派到桶内
for(int i = 0; i < array.length; i++) {
int index = array[i]/10;
list.get(index).add(array[i]);
}
//原来待排数组的索引
int index = 0;
//对每个桶里的元素进行排序
for(LinkedList<Integer> l : list) {
//判断某个桶是否有元素
int size = l.size();
if(size == 0) {
continue;
}
//将linkedList转换为数组
int[] bucketArray = new int[size];
for(int i = 0; i < bucketArray.length; i++) {
bucketArray[i] = l.get(i);
}
//随便调用一个对数组的排序方法,例如插入
insertSort(bucketArray);
//将排好序的数组放入原数组中
for(int i = 0; i < bucketArray.length; i++) {
array[index] = bucketArray[i];
index++;
}
}
System.out.println("桶:");
traversal(array);
}
/**
* 基数排序:
* 基数排序是按照低位先排序,然后收集;
* 再按照高位排序,然后再收集;
* 依次类推,直到最高位。
* 有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。
* 最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
*
* 排序过程:
* (1)取得数组中的最大数,并取得位数;
* (2)arr为原始数组,从最低位开始取每个位组成radix数组;
* (3)对radix进行计数排序(利用计数排序适用于小范围数的特点);
*
* @param array 待排序数组
* @param maxDigit 最高位数
*/
public static void radixSort(int[] array, int maxDigit) {
//基数(桶的数量)一般为10
int radix = 10;
//创建桶的数组
List<LinkedList<Integer>> list = new ArrayList<>();
for(int i = 0; i < radix; i++) {
LinkedList<Integer> bucket = new LinkedList<>();
list.add(bucket);
}
//当前分桶的所依据的位数(1是个位,2是十位,以此类推)
int pos = 1;
//进行分桶
//pos=1 value/1%10
//pos=2 value/10%10
//pos=3 value/100%10
while(pos <= maxDigit) {
//原数组的索引
int index = 0;
for(int i = 0; i < array.length; i++) {
//算出除数
int divisor = 1;
for(int j = 1; j < pos; j++) {
divisor = divisor * 10;
}
//当前索引值的某个位上的值(0-9)
int num = array[i]/divisor%10;
list.get(num).add(array[i]);
}
//把当此排好的数全部放入原待排数组中,准备下一位的基数排序
for(LinkedList<Integer> ll : list) {
int size = ll.size();
if(size == 0) {
//无数据,跳过
continue;
}
//取数据存放入原来数组中
while(size > 0) {
//取链表的第一个数是因为前一轮位数比其大的一定在后面,所以这一轮相同位数的小的在前,从头开始取
int first = ll.removeFirst();
size--;
array[index] = first;
index++;
}
}
//再算下一位
pos++;
}
System.out.println("基数:");
traversal(array);
}
}