插入排序
它的工作原理是不断将新的元素插入一个排好序的序列中。对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
class InsertionSort{
public static void insertionSort(int[] list){
//第一次循环以第一个元素为初始排好序的序列
for(int i=1;i<list.length;i++){
int currentElement = list[i];
int k;
for(k=i-1;k>=0 && list[k] > currentElement;k--){
list[k+1] = list[k];
}
list[k+1] = currentElement;//此处k+1比上面的k+1要小1
}
}
}
希尔排序
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
简单插入排序很循规蹈矩,不管数组分布是怎么样的,依然一步一步的对元素进行比较,移动,插入,比如[5,4,3,2,1,0]这种倒序序列,数组末端的0要回到首位置很是费劲,比较和移动元素均需n-1次。而希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。希尔排序通过这种策略使得整个数组在初始阶段达到从宏观上看基本有序,小的基本在前,大的基本在后。然后缩小增量,到增量为1时,其实多数情况下只需微调即可,不会涉及过多的数据移动。
代码
class ShellSort {
public static void shellSort(int[] arr) {
// 空数组 或 只有一个元素的数组,则什么都不做。
if (arr == null || arr.length <= 1) return;
// 定义希尔增量。
int gap = arr.length / 2;
// gap缩小到0的时候就退出循环。
while (gap != 0) {
// 每组进行直接插入排序。
for (int i = gap; i < arr.length; i++) { // i 代表待插入元素的索引。
int value = arr[i];
int j = i - gap; // j 代表i的上一个元素,相差一个增量gap。
// j < 0 时退出循环,说明 j 是最小的元素的索引值。
// 或者 arr[j] <= value 时退出循环,说明 j 是比value小的元素的索引值。
for (; j >= 0 && arr[j] > value; j -= gap) {
arr[j + gap] = arr[j]; // 把元素往后挪。
}
arr[j + gap] = value;
}
// 缩小增量。
gap /= 2;
}
}
}
冒泡排序
冒泡排序算法需要多次遍历数组,在每次遍历中,比较连续相邻的元素。如果某一对元素是降序,则交换它们的值;否则,保持不变。由于较小的值像“气泡”一样逐渐浮向顶部,而较大的值沉向底部,所以这种算法称为冒泡排序或下沉排序。第一次遍历之后,最后一个元素成为数组中的最大数。第二次遍历之后,倒数第二个元素成为数组中的第二大数。整个过程持续到所有元素排好序。
public static void bubbleSort(int[] list){
for(int k=0;k<list.length;k++){
for(int i=1;i<list.length - k;i++){
if(list[i]<list[i-1]){
int temp = list[i-1];
list[i-1] = list[i];
list[i] = temp;
}
}
}
}
选择排序
选择排序原理即是,遍历元素找到一个最小(或最大)的元素,把它放在第一个位置,然后再在剩余元素中找到最小(或最大)的元素,把它放在第二个位置,依次下去,完成排序。
class SelectSort {
public static void selectSort(int[] list) {
for (int i = 0; i < list.length - 1; i++) {
int minIndex = i; // 用来记录最小值的索引位置,默认值为i
for (int j = i + 1; j < list.length; j++) {
if (list[j] < list[minIndex]) {
minIndex = j; // 遍历 i+1~length 的值,找到其中最小值的位置
}
}
// 交换当前索引 i 和最小值索引 minIndex 两处的值
if (i != minIndex) {
int temp = list[i];
list[i] = list[minIndex];
list[minIndex] = temp;
}
// 执行完一次循环,当前索引 i 处的值为最小值,直到循环结束即可完成排序
}
}
}
冒泡排序和选择排序的区别:
(1)冒泡排序会交换相邻位置的两个数,而选择排序是查找序列中的最大值或者最小值;
(2)冒泡排序每一轮比较,可能会交换多组相邻元素的位置,选择排序每一轮比较都只需要换一次位置;
(3)冒泡排序是通过数去找位置,选择排序是给定位置去找数;
快速排序
快速排序的工作机制为,该算法在数组中选择一个称为基准的元素,将数组分为两部分,使得第一部分中的所有元素都小于或等于基准元素,而第二部分中的所有元素都大于基准元素。对第一部分递归地应用快速排序算法,对第二部分也递归地应用快速排序算法。
1.partition函数把数组分为大于pivot值和小于pivot值得拉ing部分,它返回pivot值最后的下标。在pivotIndex左边的值都小于pivot,右边的都大于pivot。
2.quick函数对每个子部分递归应用partition函数。
3.递归条件结束,得到排序结果
class QuickSort{
public static void quickSort(int[] list){
quickSort(list,0,list.length-1);
}
public static void quickSort(int[] list, int first, int last){
if(last>first){
int pivotIndex = partition(list,first,last);
quickSort(list,first,pivotIndex-1);
quickSort(list,pivotIndex+1,last);
}
}
public static int partition(int[] list, int first, int last){
int pivot = list[first];
int low = first+1;
int high = last;
//该循环可以实现把元素分为大于基准或小于等于基准元素这两部分的操作
while(high>low){
//从左侧查找第一个大于基准元素的元素
while(low<=high && list[low]<=pivot)
low++;
//从右侧开始查找第一个小于或等于基准元素的元素
while(high>low && list[high]>pivot)
high--;
//交换上面查找到的两个元素的位置
if(high>low){
int temp = list[high];
list[high] = list[low];
list[low] = temp;
}
//下一轮循环继续交换下一对元素
}
//从右向左,寻找划分后的数组中第一个小于基准元素的元素
while(high>first && list[high]>=pivot)
high--;
//交换基准元素与上一步查找得到的元素
if(pivot>list[high]){
list[first] = list[high];
list[high] = pivot;
return high;//返回基准元素当前的位置
}else{
return first;//基准元素恰巧是第一个
}
}
}
归并排序
归并排序是典型的分治算法。该算法将数组分为两半,对每个子数组再进行分割,直到把子数组分成最简单的形式,再对子数组元素按照大小顺序进行合并。该算法需要先把数组往下分割,再往上合并,具体的实现需要用到递归思想。
图 归并排序
class MergeSort{
//递归地进行归并排序
public static void mergeSort(int[] list){
if(list.length>1){
int[] firstHalf = new int[list.length/2];
//复制list的前半部分到firstHalf中
System.arraycopy(list,0,firstHalf,0,list.length/2);
mergeSort(firstHalf);
int secondHalfLength = list.length - list.length/2;
int[] secondHalf = new int[secondHalfLength];
System.arraycopy(list,list.length/2,secondHalf,0,secondHalfLength);
mergeSort(secondHalf);
merge(firstHalf,secondHalf,list);
}
}
//把两个数组中的元素按大小顺序合并到临时数组temp中
public static void merge(int[] list1,int[] list2,int[] temp){
//这三个整数用于获得三个数组的当前元素
int current1 = 0;
int current2 = 0;
int current3 = 0;
while(current1<list1.length && current2<list2.length){
//重复比较list1、list2中的当前元素,将较小的添加到temp中
if(list1[current1] < list2[current2]){
temp[current3++] = list1[current1++];
}else{
temp[current3++] = list2[current2++];
}
//哪个数组中有为移动到temp中的元素,就移动
while(current1<list1.length)
temp[current3++] = list1[current1++];
while(current2<list2.length)
temp[current3++] = list2[current2++];
}
}
}
堆排序
堆排序使用的是二叉堆。它首先将所有的元素添加到一个堆上,然后不断移除最大的元素以获得一个排好序的线性表。
数据结构堆是一颗完全二叉树,也就是说二叉树的每一层都是满的,或者最后一层没填满但最后一层的叶子都是靠最左放置的。堆分为两种,大顶堆和小顶堆。
大顶堆:堆中的父结点总是大于子结点,但左右子结点不区分大小。
小顶堆:堆中的父结点总是小于子结点,左右子结点不区分大小。
堆的存储:可以将堆存储在一个数组中。对于i位置的结点,它的左子结点在2i+1处,它的右子结点在位置2i+2处,而它的父结点在位置(i-1)/2处。
添加元素:为了给堆添加一个新结点,首先将它添加到堆的末尾,然后:
将最后一个结点作为当前结点;
While(当前结点大于它的父结点){
将当前结点和它的父结点交换;
现在当前结点往上进了一个层次
}
删除元素:从堆中删除元素,总是删除根结点。在删除根结点之后,就必须重建这颗树以保持堆的属性。
用最后一个结点替换根结点;
让根结点称为当前结点;
While(当前结点具有子结点并且当前结点小于它的子结点){
将当前结点和它的较大子结点交换;
现在当前结点往下退了一个层次;
}
堆的实现
public class Heap<E extends Comparable> {
private ArrayList<E> list = new ArrayList<E>();
public Heap(){
}
public Heap(E[] objects){
for(int i=0;i<objects.length;i++)
add(objects[i]);
}
public void add(E newObject){
list.add(newObject);
int currentIndex = list.size()-1;
while(currentIndex > 0){
int parentIndex = (currentIndex - 1)/2;
if(list.get(currentIndex).compareTo(list.get(parentIndex))>0){
E temp = list.get(currentIndex);
list.set(currentIndex,list.get(parentIndex));
list.set(parentIndex,temp);
}else{
break;
}
currentIndex = parentIndex;
}
}
public E remove(){
if(list.size()==0) return null;
E removedObject = list.get(0);
list.set(0,list.get(list.size()-1));
list.remove(list.size()-1);
int currentIndex = 0;
while(currentIndex<list.size()){
int leftChildIndex = 2*currentIndex+1;
int rightChildIndex = 2*currentIndex+2;
if(leftChildIndex>=list.size()) break;
int maxIndex = leftChildIndex;
if(rightChildIndex<list.size()){
if(list.get(maxIndex).compareTo(list.get(rightChildIndex))<0){
maxIndex = rightChildIndex;
}
}
if(list.get(currentIndex).compareTo(list.get(maxIndex))<0){
E temp = list.get(maxIndex);
list.set(maxIndex,list.get(currentIndex));
list.set(currentIndex,temp);
currentIndex = maxIndex;
}else{
break;
}
}
return removedObject;
}
public int getSize(){
return list.size();
}
}
堆排序
public class HeapSortDemo {
public static void main(String[] args) {
Integer[] list = {9,8,7,6,5,4,3,2,1};
HeapSort.heapSort(list);
for(int i:list){
System.out.println(i);
}
}
}
class HeapSort{
public static <E extends Comparable<E>> void heapSort(E[] list){
Heap<E> heap = new Heap<E>();
for(int i =0;i<list.length;i++){
heap.add(list[i]);
}
for(int i =list.length-1;i>=0;i--){
list[i] = heap.remove();
}
}
}
桶排序
桶排序的基本思想是假设数据在[min,max]之间均匀分布,其中min、max分别指数据中的最小值和最大值。那么将区间[min,max]等分成n份,这n个区间便称为n个桶。将数据加入对应的桶中,然后每个桶内单独排序。由于桶之间有大小关系,因此可以从大到小(或从小到大)将桶中元素放入到数组中。
按元素的最大健值与最小健值之差来创建指定数量的桶,并在每个桶中创建一个队列。
按顺序遍历待排序数组,将它们放到对应桶的队列中。
按桶编号顺序进行遍历,将每个桶中队列按顺序输出回原数组中。
class BucketSort{
public static void bucketSort(int[] arr){
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for(int i = 0; i < arr.length; i++){
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
//桶数
int bucketNum = (max - min) + 1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
for(int i = 0; i < bucketNum; i++){
bucketArr.add(new ArrayList<Integer>());
}
//将每个元素放入对应的桶
for(int i = 0; i < arr.length; i++){
int num = (arr[i] - min);
bucketArr.get(num).add(arr[i]);
}
int k =0;
for(int i=0;i<arr.length;i++){
//int num = (arr[i] - min) / (arr.length);
for(int j=0;j<bucketArr.get(i).size();j++){
if(bucketArr.get(i)!=null){
arr[k++] = bucketArr.get(i).get(j);
}
}
}
}
}
基数排序
基数排序(radix sort)是桶排序的扩展,也叫“分配式排序”(distribution sort)或“桶子法”。它将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较。
具体做法是:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
通过基数排序对数组{53, 3, 542, 748, 14, 214, 154, 63, 616},它的示意图如下:
图 基数排序
代码
class RadixSort{
public static void radixSort(int[] list){
//找出数组中最大元素的长度,这关系着给数字补到多长
int max = list[0];
for(int i=0;i<list.length;i++){
if(list[i]>max)
max=list[i];
}
int maxLength=(max+"").length();
//定义一个二位数组,其中每个一维数组是一个桶
// 总共有10个桶。每个桶容量是要排序数组的大小
int[][] bucket = new int[10][list.length];
//该数组记录每个桶中实际存放多少数据
int[] bucketElementCounts = new int[10];
for(int i=0,n=1;i<list.length;i++,n*=10){
//这段代码实现把数据装入桶
for(int j=0;j<list.length;j++){
//求数组元素的对应位的值
int digitOfElement = list[j]/n%10;
//根据对应位的值,把元素放入对应桶
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = list[j];
//哪个桶添加了元素,桶存放的数据量就加1
bucketElementCounts[digitOfElement]++;
}
//这段代码把数据从桶中取出,已实现一定的排序
int index = 0;
for(int k=0;k<bucketElementCounts.length;k++){
//桶非空,就从桶中取数据
if(bucketElementCounts[k]!=0){
for(int l=0;l<bucketElementCounts[k];l++){
//按桶编号的从小到大,取数据放到原数组
list[index++]=bucket[k][l];
}
}
//清空桶,为下一位装桶做准备
bucketElementCounts[k] = 0;
}
}
}
}