冒泡,选择,插入,shell排序算法及汉诺塔之斐波那契数列实现

三种基础的排序算法及shell排序

在计算机科学所使用的排序算法通常被分类为:

计算的 时间复杂度(最差、平均、和最好性能),依据列表(list)的大小(n)。一般而言,好的性能是O(n log n),且坏的性能是O(n^2)。对于一个排序理想的性能是O(n)。仅使用一个抽象关键比较运算的排序算法总平均上总是至少需要O(n log n)。
存储器使用量(以及其他电脑资源的使用)
稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
依据排序的方法:插入、交换、选择、合并等等。
依据排序的方法分类的三种排序算法:

冒泡排序

冒泡排序对一个需要进行排序的数组进行以下操作:

比较第一项和第二项;
如果第一项应该排在第二项之后, 那么两者交换顺序;
比较第二项和第三项;
如果第二项应该排在第三项之后, 那么两者交换顺序;
以此类推直到完成排序;
实例说明:

代码实现java:

function swap(items, firstIndex, secondIndex){
  var temp = items[firstIndex];
  items[firstIndex] = items[secondIndex];
  items[secondIndex] = temp;
};

function bubbleSort(items){
  var len = items.length, i, j, stop;

  for (i = 0; i < len; i++){
    for (j = 0, stop = len-i; j < stop; j++){
      if (items[j] > items[j+1]){
        swap(items, j, j+1);
      }
    }
  }
  return items;
}

外层的循环决定需要进行多少次遍历, 内层的循环负责数组内各项的比较, 还通过外层循环的次数和数组长度决定何时停止比较.

冒泡排序极其低效, 因为处理数据的步骤太多, 对于数组中的每n项, 都需要n^2次操作来实现该算法(实际比n^2略小, 但可以忽略, 具体原因等待提莫前去探路), 即时间复杂度为O(n^2).

对于含有n个元素的数组, 需要进行(n-1)+(n-2)+…+1次操作, 而(n-1)+(n-2)+…+1 = n(n-1)/2 = n^2/2 - n/2, 如果n趋于无限大, 那么n/2的大小对于整个算式的结果影响可以忽略, 因此最终的时间复杂度用O(n^2)表示

shell

#!/bin/bash
vs() {
    array[$1]=$[${array[$1]}^${array[$2]}]
    array[$2]=$[${array[$1]}^${array[$2]}]
    array[$1]=$[${array[$1]}^${array[$2]}]
}
read -p "请输入多个整数:" num
declare -a array
array=($num)

len=$[${#array[*]}-1]
for ((i=0; i < $len;i++));do
    sto=$[$len-$i]
    for ((j=0; j < $sto;j++));do
        if [ ${array[$j]} -gt ${array[$[$j+1]]} ];then
            vs $j $[$j+1]
        fi
    done
done
echo "${array[*]}"
选择排序

a) 原理:每一趟从待排序的记录中选出最小的元素,顺序放在已排好序的序列最后,直到全部记录排序完毕。也就是:每一趟在n-i+1(i=1,2,…n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。基于此思想的算法主要有简单选择排序、树型选择排序和堆排序。(这里只介绍常用的简单选择排序)

b) 简单选择排序的基本思想:给定数组:int[] arr={里面n个数据};第1趟排序,在待排序数据arr[1]~arr[n]中选出最小的数据,将它与arrr[1]交换;第2趟,在待排序数据arr[2]~arr[n]中选出最小的数据,将它与r[2]交换;以此类推,第i趟在待排序数据arr[i]~arr[n]中选出最小的数据,将它与r[i]交换,直到全部排序完成。

java代码实现

public class SelectionSort {
    public static void main(String[] args) {
        int[] arr={1,56,8,66,88,11};
        System.out.println("交换之前:");
        for(int num:arr){
            System.out.print(num+" ");
        }        
        //选择排序的优化
        for(int i = 0; i < arr.length - 1; i++) {// 做第i趟排序
            int k = i;
            for(int j = k + 1; j < arr.length; j++){// 选最小的记录
                if(arr[j] < arr[k]){ 
                    k = j; //记下目前找到的最小值所在的位置
                }
            }
            //在内层循环结束,也就是找到本轮循环的最小的数以后,再进行交换
            if(i != k){  //交换a[i]和a[k]
                int temp = arr[i];
                arr[i] = arr[k];
                arr[k] = temp;
            }    
        }
        System.out.println();
        System.out.println("交换后:");
        for(int num:arr){
            System.out.print(num+" ");
        }
    }

}

外层循环决定每次遍历的初始位置, 从数组的第一项开始直到最后一项. 内层循环决定哪一项元素被比较.

选择排序的时间复杂度为O(n^2).

shell

[root@cos7 app ]#cat selectSort.sh 
vs() {
    array[$1]=$[${array[$1]}^${array[$2]}]
    array[$2]=$[${array[$1]}^${array[$2]}]
    array[$1]=$[${array[$1]}^${array[$2]}]
}
read -p "请输入多个整数:" num
declare -a array
array=($num)

len=$[${#array[*]}-1]
leng=$[$len+1]
for ((i=0; i < $len;i++));do
    min=$i
    for ((j=$[$min+1]; j < $leng;j++));do
        if [ ${array[$j]} -lt ${array[$min]} ];then
            min=$j
        fi
    done
    if [ $i -ne $min ];then
            vs $i $min
    fi

done
echo "${array[*]}"
插入排序

与上述两种排序算法不同, 插入排序是稳定排序算法(stable sort algorithm), 稳定排序算法指不改变列表中相同元素的位置, 冒泡排序和选择排序不是稳定排序算法, 因为排序过程中有可能会改变相同元素位置. 对简单的值(数字或字符串)排序时, 相同元素位置改变与否影响不是很大. 而当列表中的元素是对象, 根据对象的某个属性对列表进行排序时, 使用稳定排序算法就很有必要了.

一旦算法包含交换(swap)这个步骤, 就不可能是稳定的排序算法. 列表内元素不断交换, 无法保证先前的元素排列为止一直保持原样. 而插入排序的实现过程不包含交换, 而是提取某个元素将其插入数组中正确位置.

插入排序的实现是将一个数组分为两个部分, 一部分排序完成, 一部分未进行排序. 初始状态下整个数组属于未排序部分, 排序完成部分为空. 然后进行排序, 数组内的第一项被加入排序完成部分, 由于只有一项, 自然属于排序完成状态. 然后对未完成排序的余下部分的元素进行如下操作:

如果这一项的值应该在排序完成部分最后一项元素之后, 保留这一项在原有位置开始下一步;
如果这一项的值应该排在排序完成部分最后一项元素之前, 将这一项从未完成部分暂时移开, 将已完成部分的最后一项元素移后一个位置;
被暂时移开的元素与已完成部分倒数第二项元素进行比较;
如果被移除元素的值在最后一项与倒数第二项的值之间, 那么将其插入两者之间的位置, 否则继续与前面的元素比较, 将暂移出的元素放置已完成部分合适位置. 以此类推直到所有元素都被移至排序完成部分.
实例说明:

现在需要将数组var items = [5, 2, 6, 1, 3, 9];进行插入排序:

5属于已完成部分, 余下元素为未完成部分. 接下来提取出2, 因为5比2大, 于是5被移至靠右一个位置, 覆盖2, 占用2原本存在的位置. 这样本来存放5的位置(已完成部分的首个位置)就被空出, 而2在比5小, 因此将2置于这个位置, 此时结果为[2, 5, 6, 1, 3, 9];
接下来提取出6, 因为6比5大, 所以不操作提取出1, 1与已完成部分各个元素(2, 5, 6)进行比较, 应该在2之前, 因此2, 5, 6各向右移一位, 1置于已完成部分首位, 此时结果为[1, 2, 5, 6, 3, 9];
对余下未完成元素进行类似操作, 最后得出结果[1, 2, 3, 5, 6, 9];
java代码实现:

function insertionSort(items) {
  let len = items.length, value, i, j;
  for (i = 0; i < len; i++) {
    value = items[i];
    for (j = i-1; j > -1 && items[j] > value; j--) {
      items[j+1] = items[j];
    }
    items[j+1] = value;
  }
  return items;
};

外层循环的遍历顺序是从数组的第一位到最后一位, 内层循环的遍历则是从后往前, 内层循环同时负责元素的移位.

插入排序的时间复杂度为O(n^2)

shell

[root@cos7 app ]#cat insertionSort.sh 
read -p "请输入多个整数:" num
declare -a array
array=($num)

len=$[${#array[*]}-1]
leng=$[$len+1]
for ((i=0; i < $leng;i++));do
    value=${array[$i]}
    for (( j=$[$i-1];j > -1 &&  ${array[j]} > $value;j-- ));do
        array[$[j+1]]=${array[j]}
    done
   array[$[j+1]]=$value      
done
echo "${array[*]}"

以上三种排序算法都十分低效, 因此实际应用中不要使用这三种算法, 遇到需要排序的问题, 应该首先使用JavaScript内置的方法Array.prototype.sort();

shell希尔排序

[ 参考外人博客 ]
java代码

public class ShellSort {

    public static void main(String[] args) {
        int[] array = { 49, 38, 65, 97, 76, 13, 27, 49, 55, 4 };
        shellSort(array);

        for (int i = 0; i < array.length; i++)
            System.out.print(array[i] + "  ");
    }

    public static void shellSort(int[] arr) {
        int length = arr.length;
        int d = length;
        int times = 0;
        while (true) {
            d = d / 2;
            for (int i = 0; i < d; i++)
                for (int j = i; j < length; j = j + d) {
                    int k;
                    int temp = arr[j];
                    for (k = j - d; k >= 0 && temp < arr[k]; k = k - d) {
                        arr[k + d] = arr[k];
                    }
                    arr[k+d] = temp;
                }
            times++;
            System.out.println("第" + times + "趟排序结果:");
            for (int i = 0; i < arr.length; i++)
                System.out.print(arr[i] + "  ");
            System.out.println();
            if (d == 1)
                break;

        }
    }
}

shell

[root@cos7 app ]#bash shellSort.sh 
89 12 65 97 61 81 27 2 61 98
2 12 27 61 61 65 81 89 97 98
[root@cos7 app ]#cat shellSort.sh 
#!/bin/bash
ShellSort() {
    #希尔排序 时间复杂度不确定
    #gap 差距 的意思
    #增量的选择
    for(( gap=$[${#array[*]}/2];gap > 0;gap /= 2 ));do
        #n-gap 趟排序
        for(( i=$gap;i < ${#array[*]};i++ ));do
            tmp=${array[i]};
            for(( j=$i;j >= $gap && $tmp < ${array[$[j-$gap]]};j-=$gap ));do
                array[j]=${array[$[j-$gap]]}
            done
        array[j]=$tmp
        done
    done
}
main() {
    declare -a array
    array=("89" "12" "65" "97" "61" "81" "27" "2" "61" "98")
    echo "${array[*]}"
    ShellSort $array
    echo "${array[*]}"
}
main

汉诺塔

汉诺塔问题源于印度神话
那么好多人会问64个圆盘移动到底会花多少时间?那么古代印度距离现在已经很远,这64个圆盘还没移动完么?我们来通过计算来看看要完成这个任务到底要多少时间?
我们首先利用数学上的数列知识来看看F(n=1)=1,F(n=2)=3,F(n=3)=7,F(n=4)=15……F(n)=2F(n-1)+1;
我们使用数学归纳法可以得出通项式:F(n)=2^n-1。当n为64时F(n=64)=18446744073709551615。
我们假设移动一次圆盘为一秒,那么一年为31536000秒。那么18446744073709551615/31536000约等于584942417355天,换算成年为5845.54亿年。
目前太阳寿命约为50亿年,太阳的完整寿命大约100亿年。所以我们整个人类文明都等不到移动完整圆盘的那一天。

有很多人对汉诺塔的解法产生了兴趣。从一阶汉诺塔到N阶汉诺塔它们是否有规律性的算法?
我们在使用程序实现它之前我们来分析分析汉诺塔的解法:
我们设定三个柱子A,B,C。我们的目的是将环从A–>C。

public class Hanoilmpl {
    public void hanoi(int n, char A, char B, char C) {
        if (n == 1) {
            move(A, C);
        } else {
            hanoi(n - 1, A, C, B);//步骤1 按ACB数序执行N-1的汉诺塔移动
            move(A, C);             //步骤2   执行最大盘子移动
            hanoi(n - 1, B, A, C);//步骤3 按BAC数序执行N-1的汉诺塔移动
        }
    }

    private void move(char A, char C) {//执行最大盘子的从A-C的移动
        System.out.println("move:" + A + "--->" + C);
    }

    public static void main(String[] args) {
        Hanoilmpl hanoi = new Hanoilmpl();
        System.out.println("移动汉诺塔的步骤:");
        hanoi.hanoi(3, 'a', 'b', 'c');
    }
}

shell
echo a
echo b
echo c
给一个函数,只允许输入整数1到无穷大,用变量num表示
#无论多少盘片,都是一步一步在A B C 3个圆柱间移动

move1() {
    echo "从$1$2"
}
move2() {
    if [ $1 -eq 1 ];then
        move1 $2 $4
    else
        move2 $[$1-1] $2 $4 $3
        move1 $2 $4 
        move2 $[$1-1] $3 $2 $4
    fi
} 
move2 $1 A B C

[root@cos7 app ]#bash hannuota.sh 3
从A到C
从A到B
从C到B
从A到C
从B到A
从B到C
从A到C

猜你喜欢

转载自blog.csdn.net/csdn_immortal/article/details/82085832
今日推荐