经典排序法及其复杂度分析 - JavaScript版

在这里插入图片描述

冒泡、选择、插入、希尔 详情见一下链接

JavaScript 数据结构与算法

快速排序

分析见上面同一链接
具体代码修改为下面:

//创建列表类
function ArrayList() {
    //属性
    this.array = []

    //方法
    //封装将数据插入到数组中方法
    ArrayList.prototype.insert = function(item) {
        this.array.push(item)
    }

    //toString方法
    ArrayList.prototype.toString = function() {
        return this.array.join('-')
    }

    //交换两个位置的数据
    ArrayList.prototype.swap = function(m, n) {
        let temp = this.array[m]
        this.array[m] = this.array[n]
        this.array[n] = temp
    }

    //冒泡排序
    ArrayList.prototype.bubblesor = function() {
        //1.获取数组的长度
        let length = this.array.length

        //外层循环控制冒泡趟数
        for (let j = length - 1; j >= 0; j--) {
            //内层循环控制每趟比较的次数
            for (let i = 0; i < j; i++) {
                if (this.array[i] > this.array[i + 1]) {
                    //交换两个数据
                    let temp = this.array[i]
                    this.array[i] = this.array[i + 1]
                    this.array[i + 1] = temp
                }
            }
        }
    }


    //选择排序
    ArrayList.prototype.selectionSort = function() {
        //1.获取数组的长度
        let length = this.array.length

        //2.外层循环:从0开始获取元素
        for (let j = 0; j < length - 1; j++) {
            let min = j
                //内层循环:从i+1位置开始,和后面的元素进行比较
            for (let i = min + 1; i < length; i++) {
                if (this.array[min] > this.array[i]) {
                    min = i
                }
            }
            this.swap(this.array, min, j)
        }
    }


    //插入排序
    ArrayList.prototype.insertionSort = function() {
            //1.获取数组的长度
            let length = this.array.length

            //2.外层循环:从第二个数据开始,向左边的已经局部有序数据进行插入
            for (let i = 1; i < length; i++) {
                //3.内层循环:获取i位置的元素,使用while循环(重点)与左边的局部有序数据依次进行比较
                let temp = this.array[i]
                let j = i
                while (this.array[j - 1] > temp && j > 0) {
                    this.array[j] = this.array[j - 1] //大的数据右移
                    j--
                }
                //4.while循环结束后,index = j左边的数据变为局部有序且array[j]最大。此时将array[j]重置为排序前的数据array[i],方便下一次for循环
                this.array[j] = temp
            }
        }
        //希尔排序
    ArrayList.prototype.shellSort = function() {
        //1.获取数组的长度
        let length = this.array.length

        //2.初始化增量
        let gap = Math.floor(length / 2)

        //3.第一层循环:while循环(使gap不断减小)
        while (gap >= 1) {
            //4.第二层循环:以gap为增量,进行分组,对分组进行插入排序
            //重点为:将index = gap作为选中的第一个数据
            for (let i = gap; i < length; i++) {
                let temp = this.array[i]
                let j = i
                    //5.第三层循环:寻找正确的插入位置
                while (this.array[j - gap] > temp && j > gap - 1) {
                    this.array[j] = this.array[j - gap]
                    j -= gap
                }
                //6.将j位置的元素设置为temp
                this.array[j] = temp
            }

            gap = Math.floor(gap / 2)
        }
    }

    //快速排序
    // 1.选择枢纽
    ArrayList.prototype.median = function() {
        // console.log('1 ', this.array);
        //1.取出中间的位置
        let center = Math.floor(this.array.length / 2)
        let right = this.array.length - 1
        let left = 0
            // console.log('2 ', center);

        // 2.判断大小并进行交换
        if (this.array[left] > this.array[center]) {
            this.swap(left, center)
        }
        if (this.array[center] > this.array[right]) {
            this.swap(center, right)
        }
        if (this.array[left] > this.array[center]) {
            this.swap(left, center)
        }
        // console.log('3 ', this.array);
        // 3.返回枢纽
        return center
    }
    ArrayList.prototype.QuickSort = function() {
        if (this.array.length == 0) return []

        let center = this.median(this.array)
        let c = this.array.splice(center, 1)
        let l = new ArrayList()
        let r = new ArrayList()
            // console.log('4 ', this.array);
        for (let i = 0; i < this.array.length; i++) {
            this.array[i] < c ? l.insert(this.array[i]) : r.insert(this.array[i])
        }
        // console.log('5 ', l, r);
        return this.QuickSort.apply(l).concat(c, this.QuickSort.apply(r))
    }
}

//测试类
let list = new ArrayList()

//插入元素
list.insert(66)
list.insert(88)
list.insert(12)
list.insert(87)
list.insert(100)
list.insert(5)
list.insert(566)
list.insert(23)
    // console.log(list);
console.log('finally ', list.QuickSort());

5 归并排序

“归并”的含义是将两个或两个以上的有序序列组合成一个新的有序表。假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到 n/2 (向下取整)个长度为2)的有序子序列,再两两归并。如此重复,直到得到一个长度为n的有序序列为止。这种排序方法称为2-路归并排序
在这里插入图片描述

function MergeSort(array) {
    let len = array.length;
    if (len <= 1) {
      return array;
    }
    let num = Math.floor(len / 2);
    let left = MergeSort(array.slice(0, num));
    let right = MergeSort(array.slice(num, array.length));
    return merge(left, right);
  
    function merge(left, right) {
      let [l, r] = [0, 0];
      let result = [];
      while (l < left.length && r < right.length) {
        if (left[l] < right[r]) {
          result.push(left[l]);
          l++;
        } else {
          result.push(right[r]);
          r++;
        }
      }
      result = result.concat(left.slice(l, left.length));
      result = result.concat(right.slice(r, right.length));
      return result;
    }
  }
// 归并排序
void MergeSort(int arr[], int start, int end, int * temp)
{
	if (start >= end)
		return;
	int mid = (start + end) / 2;
	MergeSort(arr, start, mid, temp);
	MergeSort(arr, mid + 1, end, temp);
 
	// 合并两个有序序列
	int length = 0; // 表示辅助空间有多少个元素
	int i_start = start;
	int i_end = mid;
	int j_start = mid + 1;
	int j_end = end;
	while (i_start <= i_end && j_start <= j_end)
	{
		if (arr[i_start] < arr[j_start])
		{
			temp[length] = arr[i_start]; 
			length++;
			i_start++;
		}
		else
		{
			temp[length] = arr[j_start];
			length++;
			j_start++;
		}
	}
	while (i_start <= i_end)
	{
		temp[length] = arr[i_start];
		i_start++;
		length++;
	}
	while (j_start <= j_end)
	{
		temp[length] = arr[j_start];
		length++;
		j_start++;
	}
	// 把辅助空间的数据放到原空间
	for (int i = 0; i < length; i++)
	{
		arr[start + i] = temp[i];
	}
}

效率分析:

  • 时间复杂度O(nlogn),空间复杂度为O(n+logn),
  • 如果非递归实现归并,则避免了递归时深度为logn的栈空间 空间复杂度为O(n)

6 归并排序

假设初始序列含有n个记录,则可以看成n个有序的子序列,每个子序列的长度为1,然后两两归并,得到(不小于n/2的最小整数)个长度为2或1的有序子序列,再两两归并,…如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。
时间复杂度为O(nlogn),空间复杂度为O(n+logn),如果非递归实现归并,则避免了递归时深度为 logn 的栈空间 空间复杂度为O(n)

7 堆排序

详解
堆是具有下列性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆;或者每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。

堆排序就是利用堆进行排序的方法.基本思想是:将待排序的序列构造成一个大顶堆.此时,整个序列的最大值就是堆顶 的根结点.将它移走(其实就是将其与堆数组的末尾元素交换, 此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素的次大值.如此反复执行,便能得到一个有序序列了。
时间复杂度为 O(nlogn), 直接在原有数组上修改,空间复杂度为O(1)

//构建堆 只调用一次,使得从根节点到任意节点路径 都是有序的
function buildHeap(elements) {
    //从最后一个 非叶子节点的 节点开始,将该节点同 其子节点 进行比较,
    //将最大的数交换与该节点,交换后,再依次向该父节点进行相同交换处理,
    //直至构建出大顶堆(升序为大顶,降序为小顶)
    // 结束后element[0]为最大值

    // 从下而上 排序 
    for (var i = Math.floor(elements.length / 2); i >= 0; i--) {
        headAdjust(elements, i, elements.length);
    }
}

//调整函数
// 从上而下 调整最大在中间节点
function headAdjust(elements, pos, len) {
    //将当前第一个节点值进行保存
    let temp = elements[pos];

    //定位到当前节点的左边的子节点
    let child = pos * 2 + 1;

    //递归,直至没有子节点为止
    while (child < len) {
        //如果当前节点有右边的子节点,并且右子节点较大,采用右子节点和当前节点进行比较
        if (child + 1 < len && elements[child] < elements[child + 1]) {
            child += 1;
        }

        //比较当前节点和最大的子节点,小于则进行值交换,交换后将当前节点定位于子节点上
        if (elements[pos] < elements[child]) {
            elements[pos] = elements[child];
            pos = child;
            child = pos * 2 + 1;
        } else {
            break;
        }

        elements[pos] = temp;
    }
}


function sort(elements) {
    //构建堆
    buildHeap(elements);

    //从数列的尾部开始进行调 整
    for (let i = elements.length - 1; i > 0; i--) {
        //堆顶永远是最大元素,故,将堆顶和尾部元素交换,将
        //最大元素保存于尾部,并且不参与后面的调整
        let temp = elements[i];
        elements[i] = elements[0];
        elements[0] = temp;

        //进行调整,将最大元素调整至堆顶
        headAdjust(elements, 0, i);
    }
}

var elements = [3, 1, 5, 7, 2, 4, 9, 6, 10, 8];
console.log('before: ' + elements);
sort(elements);
console.log(' after: ' + elements);

上述7种都是比较排序,下面3种都是非比较排序,理论上可以达到O(n),比比较排序要快,但是这3种都是有其应用背景才能发挥作用的,否则适得其反。

8 基数排序

计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。

算法的步骤如下:

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
  3. 对所有的计数累加(从C中的位置为1的元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。

9 桶排序

桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)

桶排序以下列程序进行

  1. 设置一个定量的数组当作空桶子。
  2. 寻访串行,并且把项目一个一个放到对应的桶子去。(hash)
  3. 对每个不是空的桶子进行排序。
  4. 从不是空的桶子里把项目再放回原来的串行中

10 基数排序

基数排序(英语:Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。

	
#include<stdio.h>
#include<string.h>
#include<algorithm>

using namespace std;

/*****************计数排序*******************************/
void  CountSort(int *arr, int num)
{
    int mindata = arr[0];
    int maxdata = arr[0];
    for (int i = 1; i < num; i++)
    {
        if (arr[i] > maxdata)
            maxdata = arr[i];
        if (arr[i] < mindata)
            mindata = arr[i];
    }
    
    int size = maxdata - mindata + 1;
    //申请空间并初始化为0
    int *pCount = (int *)malloc(sizeof(int) * size);
    memset(pCount, 0, sizeof(int)*size);

    //记录排序计数,每出现一次在对应位置加1
    for (int i = 0; i < num; i++)
        ++pCount[arr[i]-mindata];

    //确定不比该位置大的数据个数
    for (int i = 1; i < size; i++)
        pCount[i] += pCount[i - 1]; //加上前一个的计数

    int *pSort = (int *)malloc(sizeof(int) * num);
    memset((char*)pSort, 0, sizeof(int) * num);

    //从末尾开始拷贝是为了重复数据首先出现的排在前面,即稳定排序
    for (int i = num - 1; i >= 0; i--)
    {
        //包含自己需要减1,重复数据循环回来也需要减1
        --pCount[arr[i]-mindata];
        pSort[pCount[arr[i]-mindata]] = arr[i];
    }
    //拷贝到原数组
    for (int i = 0; i < num; i++)
        arr[i] = pSort[i];

    free(pCount);
    free(pSort);

}

/*****************桶排序*****************************/
struct Node
{
    int key_;
    struct Node *next_;
    Node(int key)
    {
        key_ = key;
        next_ = NULL;
    }
};

#define bucket_size 10 //与数组元素个数相等

void buck_sort(int arr[], int num)
{
    Node *bucket_table[bucket_size];
    memset(bucket_table, 0, sizeof(bucket_table));

    //建立每一个头节点,头节点的key保存当前桶的数据量
    for (int i = 0; i < bucket_size; i++)
        bucket_table[i] = new Node(0);
    
    int maxValue = arr[0];
    for (int i = 1; i < num; i++)
    {
        if (arr[i] > maxValue)
            maxValue = arr[i];
    }

    for (int j = 0; j < num; j++)
    {
        Node *ptr = new Node(arr[j]);//其余节点的key保存数据

        //映射函数计算桶号
        // index = (value * number_of_elements) / (maxvalue + 1)
        int index = (arr[j] * bucket_size) / (maxValue + 1);
        Node *head = bucket_table[index];
        //该桶还没有数据
        if (head->key_ == 0)
        {
            bucket_table[index]->next_ = ptr;
            (bucket_table[index]->key_)++;

        }
        else
        {
            //找到合适的位置插入
            while (head->next_ != NULL && head->next_->key_ <= ptr->key_)
                head = head->next_;
            ptr->next_ = head->next_;
            head->next_ = ptr;
            (bucket_table[index]->key_)++;
        }

    }

    //将桶中的数据拷贝回原数组
    int m, n;
    for (m = 0, n = 0;  n < num && m < bucket_size; m++, n++)
    {
        Node *ptr = bucket_table[m]->next_;
        while (ptr != NULL)
        {
            arr[n] = ptr->key_;
            ptr = ptr->next_;
            n++;
        }
        n--;
    }

    //释放分配的动态空间
    for (m = 0; m < bucket_size; m++)
    {
        Node *ptr = bucket_table[m];
        Node *tmp = NULL;
        while (ptr != NULL)
        {
            tmp = ptr->next_;
            delete ptr;
            ptr = tmp;
        }

    }

}


/****************************************************/



/******************** 基数排序LSD*********************/

void base_sort_ISD(int *arr, int num)
{
    Node *buck[10]; // 创建一个链表数组
    Node *tail[10]; //保存每条链表尾节点指针集合,
    //这样插入buck数组时就不用每次遍历到末尾
    int  i, MaxValue, kth, high, low;
    Node *ptr;
    for(MaxValue = arr[0], i = 1; i < num; i++)
        MaxValue = max(MaxValue, arr[i]);

    memset(buck, 0, sizeof(buck));
    memset(tail, 0, sizeof(buck));

    for(low = 1; high = low * 10, low < MaxValue; low *= 10)
    {
        //只要没排好序就一直排序
        for(i = 0; i < num; i++)
        {
            //往桶里放
            kth = (arr[i] % high) / low;//取出数据的某一位,作为桶的索引
            ptr = new Node(arr[i]); //创建新节点

            //接到末尾
            if (buck[kth] != NULL)
            {
                tail[kth]->next_ = ptr;
                tail[kth] = ptr;
            }
            else
            {
                buck[kth] = ptr;
                tail[kth] = ptr;
            }

        }
        //把桶中的数据放回数组中(同条链表是从头到尾)
        for (kth = 0, i = 0; kth < num; i++)
        {
            while (buck[i] != NULL)
            {
                arr[kth++] = buck[i]->key_;
                ptr = buck[i];
                buck[i] = buck[i]->next_;
                delete ptr;
            }
        }

        memset(tail, 0, sizeof(buck));

    }

}
/**************************************************************/

int main(void)
{

    int arr1[] = {10, 15, 11, 20, 15, 18, 19, 12, 14, 17};
    int size1 = sizeof(arr1) / sizeof(arr1[0]);
    CountSort(arr1, size1);
    for (int i = 0; i < size1; i++)
        printf("%d ", arr1[i]);
    printf("\n");

    int arr2[] = {54, 8, 216, 512, 27, 729, 0, 1, 343, 125};
    int size2 = sizeof(arr2) / sizeof(arr2[0]);
    base_sort_ISD(arr2, size2);
    for (int i = 0; i < size2; i++)
        printf("%d ", arr2[i]);
    printf("\n");

    int arr3[] = {49, 38, 65, 97, 76, 13, 27, 49, 132, 134};
    int size3 = sizeof(arr3) / sizeof(arr3[0]);
    buck_sort(arr3, size3);
    for (int i = 0; i < size3; i++)
        printf("%d ", arr3[i]);
    printf("\n");

    return 0;
}

参考文献

https://blog.csdn.net/jnu_simba/article/details/9705111

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值