算法篇
基础算法
测试用例:
int[] sortInt = new int[]{12,6,8,4,1,55,78,59};
1.冒泡算法
冒泡算法作为算法中最简单可理解算法之一,可谓是经典中的经典,它通过两轮循环来逐个遍历并替换相邻两个值得大小来完成。如测试用例中的 12,6,8,4,1,55,78,59 第一轮从第一个开始进行判断并颠倒替换,如我们想要该数组从小到大排序,第一轮则会 进行: 6,12,8,4,1,55,78,59 —> 6,8,12,4,1,55,78,59 -> 6,8,4,12,1,55,78,59 … 像这样依次执行。 冒泡的时间复杂度为 O(n^2), 且比较稳定。
/**
* 1.冒泡算法:正向排序
*/
public static int[] bubbleSort (int[] sortInt){
if(sortInt==null||sortInt.length==0){
return sortInt;
}
for (int i=0;i<sortInt.length;i++){
for (int j=0;j<sortInt.length-i-1;j++){
if(sortInt[j]>sortInt[j+1]){
int temp=sortInt[j+1];
sortInt[j+1]=sortInt[j];
sortInt[j]=temp;
}
}
}
return sortInt;
}
我们一起来看一下执行结果:
冒泡算法结果:i=0,j=0 ****** [6, 12, 8, 4, 1, 55, 78, 59]
冒泡算法结果:i=0,j=1 ****** [6, 8, 12, 4, 1, 55, 78, 59]
冒泡算法结果:i=0,j=2 ****** [6, 8, 4, 12, 1, 55, 78, 59]
冒泡算法结果:i=0,j=3 ****** [6, 8, 4, 1, 12, 55, 78, 59]
冒泡算法结果:i=0,j=4 ****** [6, 8, 4, 1, 12, 55, 78, 59]
冒泡算法结果:i=0,j=5 ****** [6, 8, 4, 1, 12, 55, 78, 59]
冒泡算法结果:i=0,j=6 ****** [6, 8, 4, 1, 12, 55, 59, 78]
冒泡算法结果:i=1,j=0 ****** [6, 8, 4, 1, 12, 55, 59, 78]
冒泡算法结果:i=1,j=1 ****** [6, 4, 8, 1, 12, 55, 59, 78]
冒泡算法结果:i=1,j=2 ****** [6, 4, 1, 8, 12, 55, 59, 78]
冒泡算法结果:i=1,j=3 ****** [6, 4, 1, 8, 12, 55, 59, 78]
冒泡算法结果:i=1,j=4 ****** [6, 4, 1, 8, 12, 55, 59, 78]
冒泡算法结果:i=1,j=5 ****** [6, 4, 1, 8, 12, 55, 59, 78]
冒泡算法结果:i=2,j=0 ****** [4, 6, 1, 8, 12, 55, 59, 78]
冒泡算法结果:i=2,j=1 ****** [4, 1, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=2,j=2 ****** [4, 1, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=2,j=3 ****** [4, 1, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=2,j=4 ****** [4, 1, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=3,j=0 ****** [1, 4, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=3,j=1 ****** [1, 4, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=3,j=2 ****** [1, 4, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=3,j=3 ****** [1, 4, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=4,j=0 ****** [1, 4, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=4,j=1 ****** [1, 4, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=4,j=2 ****** [1, 4, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=5,j=0 ****** [1, 4, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=5,j=1 ****** [1, 4, 6, 8, 12, 55, 59, 78]
冒泡算法结果:i=6,j=0 ****** [1, 4, 6, 8, 12, 55, 59, 78]
1.冒泡算法结果:[1, 4, 6, 8, 12, 55, 59, 78]
2.选择排序
/**
* 2.选择排序算法
*
* 选择排序的思想其实和冒泡排序有点类似,都是在一次排序后把最小的元素放到最前面。
* 但是过程不同,冒泡排序是通过相邻的比较和交换。而选择排序是通过对整体的选择。
* 举个栗子,对5,3,8,6,4这个无序序列进行简单选择排序,首先要选择5以外的最小数来和5交换,
* 也就是选择3和5交换,一次排序后就变成了3,5,8,6,4.对剩下的序列一次进行选择和交换,
* 最终就会得到一个有序序列。其实选择排序可以看成冒泡排序的优化,因为其目的相同,
* 只是选择排序只有在确定了最小数的前提下才进行交换,大大减少了交换的次数。
* 选择排序的时间复杂度为O(n^2)。
*/
public static int[] selectSort(int[] sortInt){
if(sortInt==null||sortInt.length==0){
return sortInt;
}
for (int i=0;i<sortInt.length-1;i++){
int minIdx=i;
for(int j=i+1;j<sortInt.length;j++){
//找到最小数
if(sortInt[minIdx]>sortInt[j]){
// 保存最小数的索引
minIdx = j;
}
System.out.println("选择排序:i="+i+",j="+j+ " ****** "+Arrays.toString(sortInt));
}
//判断是否需要调换位置
if(minIdx!=i){
int tmp=sortInt[i];
sortInt[i]=sortInt[minIdx];
sortInt[minIdx]=tmp;
}
}
return sortInt;
}
执行结果:
选择排序:i=0,j=1 ****** [12, 6, 8, 4, 1, 55, 3, 2]
选择排序:i=0,j=2 ****** [12, 6, 8, 4, 1, 55, 3, 2]
选择排序:i=0,j=3 ****** [12, 6, 8, 4, 1, 55, 3, 2]
选择排序:i=0,j=4 ****** [12, 6, 8, 4, 1, 55, 3, 2]
选择排序:i=0,j=5 ****** [12, 6, 8, 4, 1, 55, 3, 2]
选择排序:i=0,j=6 ****** [12, 6, 8, 4, 1, 55, 3, 2]
选择排序:i=0,j=7 ****** [12, 6, 8, 4, 1, 55, 3, 2]
选择排序:i=1,j=2 ****** [1, 6, 8, 4, 12, 55, 3, 2]
选择排序:i=1,j=3 ****** [1, 6, 8, 4, 12, 55, 3, 2]
选择排序:i=1,j=4 ****** [1, 6, 8, 4, 12, 55, 3, 2]
选择排序:i=1,j=5 ****** [1, 6, 8, 4, 12, 55, 3, 2]
选择排序:i=1,j=6 ****** [1, 6, 8, 4, 12, 55, 3, 2]
选择排序:i=1,j=7 ****** [1, 6, 8, 4, 12, 55, 3, 2]
选择排序:i=2,j=3 ****** [1, 2, 8, 4, 12, 55, 3, 6]
选择排序:i=2,j=4 ****** [1, 2, 8, 4, 12, 55, 3, 6]
选择排序:i=2,j=5 ****** [1, 2, 8, 4, 12, 55, 3, 6]
选择排序:i=2,j=6 ****** [1, 2, 8, 4, 12, 55, 3, 6]
选择排序:i=2,j=7 ****** [1, 2, 8, 4, 12, 55, 3, 6]
选择排序:i=3,j=4 ****** [1, 2, 3, 4, 12, 55, 8, 6]
选择排序:i=3,j=5 ****** [1, 2, 3, 4, 12, 55, 8, 6]
选择排序:i=3,j=6 ****** [1, 2, 3, 4, 12, 55, 8, 6]
选择排序:i=3,j=7 ****** [1, 2, 3, 4, 12, 55, 8, 6]
选择排序:i=4,j=5 ****** [1, 2, 3, 4, 12, 55, 8, 6]
选择排序:i=4,j=6 ****** [1, 2, 3, 4, 12, 55, 8, 6]
选择排序:i=4,j=7 ****** [1, 2, 3, 4, 12, 55, 8, 6]
选择排序:i=5,j=6 ****** [1, 2, 3, 4, 6, 55, 8, 12]
选择排序:i=5,j=7 ****** [1, 2, 3, 4, 6, 55, 8, 12]
选择排序:i=6,j=7 ****** [1, 2, 3, 4, 6, 8, 55, 12]
2.选择排序:[1, 2, 3, 4, 6, 8, 12, 55]
3.快速排序
* 3.快速排序(冒泡+分治法)
* 其实快速排序的思想是来源于冒泡排序,
* 冒泡排序是通过对比相邻的两位大小然后把最小(大)的冒泡到顶端,
* 而快速排序的话是比较和交换小数和大数,这样一来不仅把小的冒泡到了
* 最前面还可以把大数沉到下面去。
*
* 举个栗子:对5,3,8,6,4这个无序序列进行快速排序,思路是右指针找比基准数小的,左指针找比基准数大的,交换之。
* 5,3,8,6,4 用5作为比较的基准,最终会把5小的移动到5的左边,比5大的移动到5的右边。
* 5,3,8,6,4 首先设置i,j两个指针分别指向两端,j指针先扫描(思考一下为什么?)4比5小停止。然后i扫描,8比5大停止。交换i,j位置。
* 5,3,4,6,8 然后j指针再扫描,这时j扫描4时两指针相遇。停止。然后交换4和基准数。
* 4,3,5,6,8 一次划分后达到了左边比5小,右边比5大的目的。之后对左右子序列递归排序,最终得到有序序列。
* 上面留下来了一个问题为什么一定要j指针先动呢?首先这也不是绝对的,这取决于基准数的位置,因为在最后两个指针相遇的时候,
* 要交换基准数到相遇的位置。一般选取第一个数作为基准数,那么就是在左边,所以最后相遇的数要和基准数交换,
* 那么相遇的数一定要比基准数小。所以j指针先移动才能先找到比基准数小的数。
* 快速排序是不稳定的,其时间平均时间复杂度是O(nlgn)。
public static int[] quickSort(int[] sortInt,int left,int right){
int i,j,temp,t;
if(left>right){
return sortInt;
}
i=left;
j=right;
//temp是基准数
temp=sortInt[left];
while(i<j){
//先从右边往左递减,查询比中间数小的数放到左侧
while (temp<=sortInt[j]&&i<j){
j--;
}
//再从左往右看数字,把大数放右侧
while(temp>=sortInt[i]&&i<j){
i++;
}
//如果满足条件则交换
if (i<j) {
t = sortInt[j];
sortInt[j] = sortInt[i];
sortInt[i] = t;
}
System.out.println("快速排序:i="+i+",j="+j+ " ****** "+Arrays.toString(sortInt));
}
//最后将基准为与i和j相等位置的数字交换
sortInt[left] = sortInt[i];
sortInt[i] = temp;
//递归调用左半数组
quickSort(sortInt, left, j-1);
//递归调用右半数组
quickSort(sortInt, j+1, right);
return sortInt;
}
//调用方法
int[] quickSort=BaseAlgorithm.quickSort(sortInt,0,sortInt.length-1);
System.out.println("3.快速排序:"+Arrays.toString(quickSort));
结果:
快速排序:i=5,j=7 ****** [12, 6, 8, 4, 1, 2, 3, 55]
快速排序:i=6,j=6 ****** [12, 6, 8, 4, 1, 2, 3, 55]
快速排序:i=1,j=5 ****** [3, 2, 8, 4, 1, 6, 12, 55]
快速排序:i=2,j=4 ****** [3, 2, 1, 4, 8, 6, 12, 55]
快速排序:i=2,j=2 ****** [3, 2, 1, 4, 8, 6, 12, 55]
快速排序:i=0,j=0 ****** [1, 2, 3, 4, 8, 6, 12, 55]
快速排序:i=3,j=3 ****** [1, 2, 3, 4, 8, 6, 12, 55]
快速排序:i=5,j=5 ****** [1, 2, 3, 4, 8, 6, 12, 55]
3.快速排序:[1, 2, 3, 4, 6, 8, 12, 55]
4.插入排序
* 插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,
* 因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,
* 对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
*
* 插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。
*
* -----算法步骤:
* 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
*
* 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。
*(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
public static int[] insertSort(int[] sortInt){
//首先,对数组进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sortInt,sortInt.length);
//从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
for(int i=1;i<arr.length;i++){
//记录要插入的位置
int tmp=arr[i];
//从已经排序的序列最右边开始比较,找到比其小的数
int j=i;
while(j>0 && tmp<arr[j-1]){
arr[j]=arr[j-1];
j--;
}
//存在比其小的数,插入
if(j!=i){
arr[j]=tmp;
}
System.out.println("插入排序:i="+i+",j="+j+ " ****** "+Arrays.toString(arr));
}
return arr;
}
5.希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
实施步骤:
选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
按增量序列个数 k,对序列进行 k 趟排序;
每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
public static int[] shellSort(int[] sortInt){
int length=sortInt.length;
int temp;
// step/=2 向下取整,如 step 为 3 ,则结果为1 ;如 step 为 7 ,则结果为3
for(int step=length/2;step>=1;step/=2){
for(int i=step;i<length;i++){
temp=sortInt[i];
int j=i-step;
while (j>0&&sortInt[j]>temp){
sortInt[j+step]=sortInt[j];
j-=step;
}
sortInt[j+step]=temp;
System.out.println("插入排序:i="+i+",j="+j+ " ****** "+Arrays.toString(sortInt));
}
}
return sortInt;
}
6.归并排序
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
自下而上的迭代;
在《数据结构与算法 JavaScript 描述》中,作者给出了自下而上的迭代方法。但是对于递归法,作者却认为:
However, it is not possible to do so in JavaScript, as the recursion goes too deep for the language to handle.
然而,在 JavaScript 中这种方式不太可行,因为这个算法的递归深度对它来讲太深了。
说实话,我不太理解这句话。意思是 JavaScript 编译器内存太小,递归太深容易造成内存溢出吗?还望有大神能够指教。
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。
算法步骤
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
设定两个指针,最初位置分别为两个已经排序序列的起始位置;
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
重复步骤 3 直到某一指针达到序列尾;
将另一序列剩下的所有元素直接复制到合并序列尾。
public static int[] mergeSort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
if (arr.length < 2) {
return arr;
}
int middle = (int) Math.floor(arr.length / 2);
int[] left = Arrays.copyOfRange(arr, 0, middle);
int[] right = Arrays.copyOfRange(arr, middle, arr.length);
return merge(mergeSort(left), mergeSort(right));
}
protected static int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
int i = 0;
while (left.length > 0 && right.length > 0) {
if (left[0] <= right[0]) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
} else {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
}
while (left.length > 0) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
}
while (right.length > 0) {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
return result;
}
7.堆排序
/**
* 7.堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,
* 并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排
* 序。分为两种方法:
*
* 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
* 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
* 堆排序的平均时间复杂度为 Ο(nlogn)。
*
* 算法步骤
* 创建一个堆 H[0……n-1];
*
* 把堆首(最大值)和堆尾互换;
*
* 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
*
* 重复步骤 2,直到堆的尺寸为 1。
* @param sourceArray
* @return
* @throws Exception
*/
public static int[] heapSort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int len = arr.length;
buildMaxHeap(arr, len);
for (int i = len - 1; i > 0; i--) {
swap(arr, 0, i);
len--;
heapify(arr, 0, len);
}
return arr;
}
private static void buildMaxHeap(int[] arr, int len) {
for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
heapify(arr, i, len);
}
}
private static void heapify(int[] arr, int i, int len) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;
if (left < len && arr[left] > arr[largest]) {
largest = left;
}
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {
swap(arr, i, largest);
heapify(arr, largest, len);
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}