排序算法(冒泡、选择、插入、计数、堆、快排、归并)

1.冒泡排序

1.1 思路

        从左往右两两比较,将最大的数放在待排序元素的最后一位,重复此操作,直到数组有序。

例如现有一个数组为[8,6,12,4,3],进行第一次冒泡排序的过程如下图所示。

        排序后的结果为[6,8,4,3,12],这样就将最大的元素放到了数组最后,待排序的数量减一,然后对待排序的数组重复上述操作。

1.2 代码及优化代码

void bubbleSort(vector<int>& v) {
    int n = v.size();
    for (int i = 1; i < n; i++) {         // 代表排序趟数
        for (int j = 0; j < n - i; j++) { // 待排序序列下标
            if (v[j] > v[j + 1])
                swap(v[j], v[j + 1]);
        }
    }
}

优化思路:如果在某次循环中没有进行交换操作,则说明该数组已经是有序的。

优化代码:

void bubbleSort(vector<int>& v) {
    int n = v.size();
    for (int i = 1; i < n; i++) {
        bool flag = 0;    //用来标记是否进行交换操作
        for (int j = 0; j < n - i; j++) {
            if (v[j] > v[j + 1]) {
                swap(v[j], v[j + 1]);
                flag = 1;
            }
        }
        if (flag == 0)   //如果没有发生交换退出整个循环
            break;
    }
}

1.3 时间复杂度及稳定性分析

时间复杂度:

        最好情况:O(n)       本身就是有序的,只需要进行一趟排序,n-1次比较。

        最坏情况:O(n^{2})      数组逆序,n-1趟排序。

稳定性:稳定

2.选择排序

 2.1 思路

        每次选择一个最大的元素,将其放到待排序数组的最后一个位置。用一个变量index(初始化为0)记录最大元素的下标,遍历到待排序数组的末尾时,和这个元素进行交换,重复此操作。

2.2 代码

void selectSort(vector<int>& v) {
    int n = v.size();
    for (int i = 1; i < n; i++) {
        int index = 0;    //用index记录最大下标
        for (int j = 1; j <= n - i; j++) {
            if (v[j] > v[index])
                index = j;    
        }
        if (index != n - i)        //如果最后一个元素不是最大元素就交换
            swap(v[index], v[n - i]);
    }
}

2.3 时间复杂度及稳定性分析

时间复杂度:O(n^{2})    没有最好和最坏

稳定性:不稳定

3.插入排序    

3.1 思路

        将整个数组划分为两区,有序区和无序区,每次从无序区中取出一个元素,将其插入到有序区的适当的位置,在最开始的时候有序区只有一个元素,无序区有n-1个元素。

        每遍历一次,有序区的元素个数增加一个,无序区的元素个数减少一个,重复n-1次,完成排序。

        以下面这个图为例子,一个大小为5的数组,一共要进行四趟循环。红色区域代表有序区,白色区域代表无序区,白色区域的第一个元素表示的是要插入的那个元素。

3.2 代码

     

void insertSort(vector<int>& v) {
    int n = v.size();
    for (int i = 1; i < n; i++) {
        int j = i - 1; // 表示有序区的最后一个元素
        int temp = v[i]; // 存无序区的第一个元素,防止后移时被覆盖
        for ( : j >= 0; j--) {
            if (v[j] > temp) {
                v[j + 1] = v[j]; // 有序区元素向后挪
            } else
                break;
        }
        v[j + 1] = temp; // 插入那个元素
    }
}

   3.3 时间复杂度及稳定性分析

        时间复杂度:

                最好情况:O(n) ,数组本身有序,每趟只需要进行一次比较

                最坏情况:O(n^{2}) ,数组逆序,有多少元素就需要进行多少次比较

        稳定性:稳定

4.计数排序(桶排序)

4.1 思路

        另外开辟一个数组,用来统计原数组中每个元素出现的次数。然后遍历原数组,将遍历到的元素覆盖到原数组即可完成排序。

        计数排序的步骤为:

                1.遍历原数组,找到最大值max

                2.开辟数组大小为max+1大小的数组

                3.遍历原数组,统计原数组中元素出现的次数(新数组的下标就代表值,数组中的元素大小代表元素出现的次数)

                4.遍历新数组,覆盖原数组。(只要新数组的元素大小不为0,就往元素组中插入该元素的下标,同时让其大小减一,直到它等于0)

如下图的数组,先找到最大值8,然后开辟一个大小为9的数组,遍历原数组统计元素出现的次数,然后遍历新数组覆盖原数组。

4.2 代码

int maxval(vector<int>& v) {  //找原数组的最大值
    int max = v[0];
    int n = v.size();
    for (int i = 1; i < n; i++)
        if (v[i] > max)
            max = v[i];
    return max;
}
void countSort(vector<int>& v) {
    int n = v.size();
    int max = maxval(v);
    vector<int> bucket(max + 1);    //开辟大小为max+1的新数组,用于统计元素出现的个数
    for (int i = 0; i < n; i++) {    //统计元素出现的个数
        bucket[v[i]]++;
    }
    int index = 0;
    for (int i = 0; i <= max; i++) {    //遍历新数组,覆盖原数组
        while (bucket[i]--) {
            v[index++] = i;
        }
    }
}

4.3 时间复杂度及稳定性分析

        时间复杂度:O(n)

        稳定性:默认是稳定性的,没有发生交换。

5.堆排序

5.1 思路

        1.将待排序数组形象成一个堆结构,并将其调整为最大堆(最小堆)

                (堆结构:完全二叉树,做孩子的下标是2i+1,右孩子2i+2)

                (最大堆:任何一个父亲结点的值都大于其孩子结点)

        2.将堆顶元素和待排序数组最后一个元素进行交换

        3.待排序的数据量减一,将排序数组重新调整为最大堆结构,重复上述步骤,循环

        n-1次将所有数据排序完成。

以下述数组为例,图一先初始化堆结构

然后再对这个堆结构进行调整,将其转化成最大堆,过程如果所示

先从最后一个父亲结点开始调整,如图1,发现子节点比父亲结点大,就进行交换。然后对倒数第二个父亲结点调整,以此类推。最后得到最终的最大堆结构,将堆顶的元素与最后一个元素进行交换,待排序的数组减少1,下次再进行最大堆调整的堆结构就如图6所示。直到最后只剩一个元素,该数组完成排序。

5.2 代码

void adjust(vector<int>& v, int start, int end) {    //调整最大堆
    int father = start;
    int child = 2 * father + 1; // 左孩子
    while (child <= end) { // 保证孩子是存在的才能进行堆结构调整
        if (child + 1 <= end && v[child + 1] > v[child])
            child++;
        // child是孩子当中最大值的小标
        if (v[child] > v[father]) {
            swap(v[chaild], v[father]);
            father = child;
            child = father * 2 + 1;
        } else
            break;
    }
}

void heapSort(vector<int>& v) {
    int n = v.size();
    // 初始化形成最大堆
    for (int i = n / 2 - 1; i >= 0; i--) { // 从最后一个父亲结点开始调整
        adjust(v, i, n - 1);
    }
    // 拿堆顶元素和最后一个元素发生交换
    for (int i = n - 1; i >= 1; i--) { // j表示的是待排序序列的最后一个元素的下标
        swap(v[0], v[i]);
        // 重新调整堆结构
        adjust(v, 0, i - 1);
    }
}

5.3 时间复杂度及稳定性分析

时间复杂度:O(n\log_{2}^{}\textrm{n})

稳定性:不稳定

6.快速排序

6.1 思路    

        通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,已达到整个序列有序。一趟快速排序的具体过程可描述为:从待排序列中任意选取一个记录(通常选取第一个记录)作为基准值,然后将记录中关键字比它小的记录都安置在它的位置之前,将记录中关键字比它大的记录都安置在它的位置之后。这样,以该基准值为分界线,将待排序列分成的两个子序列。

步骤:1.从数组中选择一个元素,一般是数组元素的第一个值,这个元素被称为基准值

2.找出比基准值小的元素以及比基准值大的元素

3.重复上述步骤

6.2 代码

void quicksort(vector<int>&v,int start,int end){
    if(start>=end){
        return;    
    }
    int temp=v[start];    //区域内的第一个元素定位基准值
    int i=start-1,j=end+1;
    int index=start;
    while(index<j){
        if(v[index]==temp){
            index++;        
        }else if(v[index]>temp){
            swap(v[--j],v[index]);        
        }else{
            swap(v[++i],v[index++]);        
        }
    }
    quicksort(v,start,i);
    quicksort(v,j,end);
}

6.3 时间复杂度及稳定性分析

时间复杂度:O(n\log_{2}^{}\textrm{n})

稳定性:不稳定

7.归并排序

    7.1 思路

        思想:将两个有序的数组合并成一个有序的数组。
第一步:
        将数组进行分解,当分解成单个元素为一组的时候才是组内有序的,
第二步:
        将两两有序的数组进行合并,将两个有序数组合并成一个有序数组。重复第二步,直至排序完
成。
合并的步骤:先申请两数组合并后那么大小的空间,然后将两个排好序的数组逐一进行比较
往申请空间里面放。

7.2 代码

void merge(vector<int>& v, int L, int mid, int R) {//合并两个有序数组
	int n = R - L + 1;
	vector<int> temp(n);
	int index = 0;
	int i = L, j =mid + 1;//控制左右下标
	while (i <= mid&&j<=R) {
		if (v[i] <= v[j])
			temp[index++] = v[i++];
		else
			temp[index++] = v[j++];
	}
	while (i <= mid) {
		temp[index++] = v[i++];
	}
	while (j <= R) {
		temp[index++] = v[j++];
	}
	i = L;
	for (index= 0; index < n; index++) {    //将temp数组复制给原数组
		v[i++] = temp[index];
	}
}
void merg(vector<int>& v,int L, int R){
	if (L == R) return;//区域内只剩一个元素
	//当区域内元素多于一个,需要找中间位置进行分割
	int mid = (R - L) / 2 + L;
	merg(v, L, mid);//向左分割
	merg(v, mid + 1, R);//向右分割
	merge(v, L, mid, R);//合并
}

7.3 时间复杂度及稳定性分析

时间复杂度:O(n\log_{2}^{}\textrm{n})

稳定性:稳定

总结

几种排序算法的比较。

名称

最差

最优

稳定性

冒泡排序

O(n)

O(n2)

稳定

选择排序

O(n2)

不稳定

插入排序

O(n)

O(n2)

稳定

计数排序

O(n)

稳定

堆排序

O(nlogn)

不稳定

快速排序

O(nlogn)

O(n2)

不稳定

归并排序

O(nlogn)

稳定

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值