【数据结构】--- 排序算法总结

  1. 冒泡排序
  2. 选择排序
  3. 堆排序
  4. 插入排序
  5. 希尔排序
  6. 归并排序
  7. 快速排序
  8. 排序算法的稳定性

冒泡排序


冒泡排序:

一个要排序的数列,将其遍历若干次,每次遍历时都将相邻的两个元素进行比较,将较大/较小的元素放在进行比较两元素后者的位置,这样一次遍历结束后,最大/最小的元素就放在了数列的最后位置。第二次遍历后,第二大/小的元素放在数组倒数第二个位置,重复多次操作做,便可得到一个有序数列。

代码实现

方案一:

void Bubble_Sort(int arr[], int n)
{
    int i = 0;
    int j = 0;
    for (; i < n; i++)
    {
        for (j = 0; j < n-1-i; j++)
        {
            if (arr[j] < arr[j+1])
            {
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
}

方案二:

void Bubble_Sort(int arr[], int n)
{
    int i = 0;
    int j = 0;
    int flag = 0;
    for (; i < n; i++)
    {
        for (j = 0; j < n-1-i; j++)
        {
            if (arr[j] < arr[j+1])
            {
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
                flag++;
            }
        }
        if (flag == 0)
        {
            break; // 数组未发生交换
        }
    }
}

时间复杂度

O(N^2)(最坏情况),最好情况时间复杂度为O(N)

空间复杂度

O(1)


选择排序


选择排序:

选择排序在每一次遍历数组时,找出数组元素中数值最小/最大的元素放置数组前端,直到所有待排序的数组有序

代码实现:

void Select_Sort(int arr[], int n)
{
    int i = 0;
    int j = 0;
    int min = 0;
    for (; i < n; i++)
    {
        min = i;
        for (j = i+1; j < n; j++)
        {
            if (arr[j] < arr[min])
            {
                min = j;
            }
        }
        if (min != i)
        {
        int tmp = arr[i];
        arr[i] = arr[min];
        arr[min] = tmp;
        }
    }
}

时间复杂度

假设数组有N个数,循环内比较次数为N-1,N-2,N-3…,1,得到一个等差数列,求和后得
(N-1+-1)*N/2
时间复杂度为O(n^2)

空间复杂度

O(1)

选择排序与冒泡排序比较:

1.两排序方式的时间复杂度相同,但在交换时,选择排序最多交换N-1次交换,而冒泡排序最多发生N^2次,因此,选择排序性能上优于冒泡排序
2.选择排序的思想比冒泡排序更易于理解


堆排序


具有以下性质的完全二叉树:
1.每个结点的值都大于等于其左右子节点的值,称为大顶堆
2.每个结点的值都小于等于其左右子节点的值,称为小顶堆

堆排序:

将待排序序列构造成一个大顶堆/小顶堆,根节点就是该序列里面最大/最小的元素,将其与末尾元素进行交换,此时末尾元素即为该序列的最大/最小元素。
将剩余元素重新构造成一个大顶堆/小顶堆,重复执行上面的过程,得到最终的有序序列

代码实现:

void AdjustDown(int arr[], int index, int n)
{
    int parent = index;
    int child = parent * 2 + 1;
    while (child < n)
    {
        if (child + 1 < n && arr[child] > arr[child+1])
        {
            child++;
        }
        if (arr[parent] > arr[child] )
        {
            int tmp = arr[parent];
            arr[parent] = arr[child];
            arr[child] = tmp;
        }
        else
        {
            break;
        }
        parent = child;
        child = parent * 2 + 1;
    }   
}

void HeapSort(int arr[], int n)
{
    //将数组转化为堆
    //最后一个非叶子节点
    int index = n / 2 - 1;
    while (index)
    {
        AdjustDown(arr, index--, n);
    }
    AdjustDown(arr, index, n);
    while (n > 1)
    {
        //交换末尾与根结点, 降序,小顶堆
        int tmp = arr[n-1];
        arr[n-1] = arr[0];
        arr[0] = tmp;
        n--;
        AdjustDown(arr, index, n);
    }
}

时间复杂度:

初始化建堆的时间复杂度为O(n),排序重建堆的时间复杂度为nlog(n),总的时间复杂度为=O(nlogn)

空间复杂度:

O(1)


插入排序


插入排序:

插入排序是基于比较的排序,插入排序每一步都将一个待排数据插入到已经排号的数据序列中

代码实现:

void Insert_Sort(int arr[], int n)
{
    int i = 0;
    int j = 0;
    for (i = 1; i < n; i++)
    {
        if (arr[i-1] > arr[i])
        {
            int tmp = arr[i];
            j = i;
            while (j > 0 && arr[j-1] > tmp)
            {
                arr[j] = arr[j-1]; 
                //后移一位留出空位插入
                j--;
            }
            arr[j] = tmp;
        }
    }   
}

时间复杂度:

比较次数为1,2,3,…, N-1,N,时间复杂度为O(N^2)(最坏情况)
O(N)(最好情况,数组有序)

空间复杂度:

O(1)


希尔排序


希尔排序:

也称缩小增量排序,是直接插入排序的一种改进版本,通过记录下标的一定增量进行分组(增量没有特别好的选择,希尔建议的增量是长度的1/2,每次增量的缩小也是1/2),按比较的大小交换分组内的数据,缩小增量改变分组后重复过程,得到有序序列

代码实现:

void Shell_Sort(int arr[], size_t len)
{
    assert(arr);
    size_t gap = len; //增量
    size_t i = 0;
    while (gap > 1)
    {
        gap = gap/2;
        for (i = gap; i < len; i++)
        {
            int key = arr[i];
            int j = i - gap;
            while (j >= 0 && key < arr[j])
            {
                int tmp = arr[j];
                arr[j] = arr[j+gap];
                arr[j+gap] = tmp;
                j = j-gap;
            }
        }
    }
}

时间复杂度:

最好情况为O(N),最坏情况为O(N^2),平均情况为O(N^1.3)

空间复杂度:

O(1)


归并排序


归并排序:

通过分治法的思想,将一个序列不断平均分为两个序列,直到再也没办法分割时,每个序列分别有序,然后依次将序列有序合并,直到合并完成
代码实现:

void _Merge_Sort(int arr[], int left, int right, int *tmp)
{
    assert(arr);
    assert(tmp);
    int mid = left + (right - left)/2;
    int i = left;
    int j = mid;
    int k = left;
    while (i < mid && j < right)
    {
        if (arr[i] < arr[j])
        {
            tmp[k++] = arr[i++];
        }
        else
        {
            tmp[k++] = arr[j++];
        }
    }
    while (i < mid)
    {
        tmp[k++] = arr[i++];
    }
    while (j < right)
    {
        tmp[k++] = arr[j++];
    }
    memcpy(arr + left, tmp + left, sizeof(int)* (right - left));
}

void sort(int arr[], int left, int right, int *tmp)
{
    assert(arr);
    assert(tmp);
    if (right- left<=1)
    {
        return;
    }
    int mid = left +(right-left)/2;
    sort(arr,left,mid,tmp);
    sort(arr,mid, right,tmp);
    _Merge_Sort(arr,left,right,tmp);

}

void Merge_Sort(int arr[], int size)
{
    assert(arr);
    int *tmp = (int*)malloc(sizeof(arr)*size);
    sort(arr,0, size,tmp);
    free(tmp);
}

时间复杂度:

O(NlogN)

空间复杂度:

O(1)


快速排序


快速排序:

对冒泡排序的改进,通过一个标准值(一般为最左或最右),将比它小的都放置左边,比它大的都放在右边,然后更改标准值再次分段进行排序

void Swap2(int* a, int* b) {
    assert(a);
    assert(b);
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

int Partion(int arr[], int _left, int _right) {
    assert(arr);
    int left = _left;
    int right = _right - 1;
    int key = arr[right];
    while (left < right) {
        /*
        **从左往右找到比key值大的元素
        */
        while (left < right && arr[left] <= key) {
            ++left;
        }
        /*
        **从右往左找到比key值小的元素
        */
        while (left < right && arr[right] >= key) {
            --right;
        }
        /*
        **交换
        */
        if (left < right) {
            Swap2(&arr[left], &arr[right]);
        }
    }
    /*
    **此时left指向的值一定大于key
    **如果是以为++left导致循环退出(如果没找到,那么肯定left >= right)
    **而right在上一次循环中已经是一个大于key的值了
    **如果是因为--right导致循环退出(和上面的情况一样)
    **left在上面的循环中已经找到了大于key的值
    */
    //所以left指向的值一定大于key
    Swap2(&arr[left], &arr[_right - 1]);
    return left;
}

void QuickSort1(int arr[], int left, int right) {
    assert(arr);
    if (right - left <= 1) {
        return;
    }
    /*
    **具体思路如下:
    **每次都找出一个基准key值,然后把数组分为两半
    **左边是小于key的元素,右边是大于key的元素
    **再进行递归的处理,直到所有的元素都有序了
    */
    int mid = Partion(arr, left, right);
    QuickSort1(arr, left, mid);
    QuickSort1(arr, mid + 1, right);
}

时间复杂度:

在最差情况下,划分由 n 个元素构成的数组需要进行 n 次比较和 n 次移动。因此划分所需时间为 O(n) 。最差情况下,每次主元会将数组划分为一个大的子数组和一个空数组。这个大的子数组的规模是在上次划分的子数组的规模减 1 。该算法需要 (n-1)+(n-2)+…+2+1= O(n^2) 时间。
在最佳情况下,每次主元将数组划分为规模大致相等的两部分。设 T(n) 表示使用快速排序算法对包含 n 个元素的数组排序所需的时间,因此,和归并排序的分析相似,快速排序的 T(n)= O(nlogn)。

空间复杂度:

O(logn)


排序算法的稳定性


假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的

对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法

堆排序、快速排序、希尔排序、直接选择排序不是稳定的排序算法
冒泡排序、直接插入排序、归并排序是稳定的排序算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值