八大排序算法合集 (归并排序、交换排序、插入排序、选择排序......)


内部排序,外部排序

若待排序记录都在内存中,称为内部排序;

若待排序记录一部分在内存,一部分在外存,则称为外部排序。

注:外部排序时,要将数据分批调入内存来排序,中间结果还要及时放入外存,显然外部排序要复杂得多。

排序算法的稳定性根据相同元素排序前后的顺序是否改变来确定

一、归并排序

递归实现 - > 自上向下
非递归排序 - > 自下向上
时间复杂度:O(NlogN)O(NlogN)

先分再合

/* 将序列对半拆分直到序列长度为1*/
void MergeSort_UptoDown(int *num, int start, int end)
{
    int mid = start + (end - start) / 2;
    if (start >= end)
        return;
    MergeSort_UptoDown(num, start, mid);
    MergeSort_UptoDown(num, mid + 1, end);
    Merge(num, start, mid, end);
}

void Merge(int *num, int start, int mid, int end)
{
    int *temp = (int *)malloc((end-start+1) * sizeof(int));    //申请空间来存放两个有序区归并后的临时区域
    int i = start;
    int j = mid + 1;
    int k = 0;
    while (i <= mid && j <= end)
        if (num[i] <= num[j])
            temp[k++] = num[i++];
        else
            temp[k++] = num[j++];
    while (i <= mid)
        temp[k++] = num[i++];
    while (j <= end)
        temp[k++] = num[j++];
    //将临时区域中排序后的元素,整合到原数组中
    for (i = 0; i < k; i++)
        num[start + i] = temp[i];
    free(temp);
}

二、交换排序

1.快速排序

基本思想:

任取一个元素 (如第一个) 为中心
所有比它小的元素一律前放,比它大的元素一律后放,形成左右两个子表;
对各子表重新选择中心元素并依此规则调整,直到每个子表的元素只剩一个

时间复杂度O(NlogN)O(NlogN)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
int n,a[maxn];
void qsorts(int l,int r)
{
    int mid=a[(l+r)/2];
    int i=l,j=r;
    do{
        while(a[i]<mid)i++;
        while(a[j]>mid)j--;
        if(i<=j)
        {
            swap(a[i],a[j]);
            i++;
            j--;
        }
    }while(i<=j);
    if(j>l)qsorts(l,j);
    if(i<r)qsorts(i,r);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    qsorts(1,n);
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    cout<<endl;
}

2.冒泡排序

O(N2)O(N^2)

void bubble_sort(int a[], int n)  
{
    int i,j,temp;    
    for (j=0;j<n-1;j++)    
    {                           
        for (i=0;i<n-1-j;i++)
        {
            if(a[i]>a[i+1])
            {
                temp=a[i];    
                a[i]=a[i+1];    
                a[i+1]=temp;
            }
        }
    }    
}

三、插入排序

每步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入为止。
即边插入边排序,保证子序列中随时都是排好序的

1.直接插入排序(基于顺序查找)

void insertSort(int* a,int T){
    int tmp,p;
    for(int i=1;i<T;i++){
        tmp=a[i];
        p=i-1;
        while(p>=0&&tmp<a[p]){
            a[p+1]=a[p];
            p--;
        }
        a[p+1]=tmp;
    }
}

2.折半插入排序(基于折半查找)

(1)基本思想

折半插入排序的基本思想是:顺序地把待排序的序列中的各个元素按其关键字的大小,通过折半查找插入到已排序的序列的适当位置。

(2)运行过程

直接插入排序的运作如下:

1、将待排序序列的第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

2、从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置,在查找元素的适当位置时,采用了折半查找方法。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

void binary_insertion_sort(int arr[], int len) 
{
        int i, j, temp, m, low, high;
        for (i = 1; i < len; i++)
        {
               temp = arr[i];
               low = 0; high = i-1;
               while (low <= high)
               {
                      m = (low +high) / 2;
                      if(arr[m] > temp)
                               high = m-1;
                      else
                               low = m+1;
               }
         }
         for (j = i-1; j>=high+1; j--)
               arr[j+1] = arr[j];
         arr[j+1] = temp;
}

3.希尔排序(基于逐趟缩小增量)

#include <stdio.h>
#include <malloc.h>
void shellSort(int *a, int len)
{
    int i, j, k, tmp, gap;  // gap 为步长
    for (gap = len / 2; gap > 0; gap /= 2) {  // 步长初始化为数组长度的一半,每次遍历后步长减半,
    	for (i = 0; i < gap; ++i) { // 变量 i 为每次分组的第一个元素下标 
	        for (j = i + gap; j < len; j += gap) { //对步长为gap的元素进行直插排序,当gap为1时,就是直插排序
	            tmp = a[j];  // 备份a[i]的值
	            k = j - gap;  // j初始化为i的前一个元素(与i相差gap长度)
	            while (k >= 0 && a[k] > tmp) {
	                a[k + gap] = a[k]; // 将在a[i]前且比tmp的值大的元素向后移动一位
	                k -= gap;
	            }
	            a[k + gap] = tmp; 
	        }
	    }
    }
}
int main(void)
{
    int i, len, * a;
    printf("请输入要排的数的个数:");
    scanf("%d",&len);
    a = (int *)malloc(len * sizeof(int)); // 动态定义数组
    printf("请输入要排的数:\n");
    for (i = 0; i < len; i++) { // 数组值的输入
        scanf("%d",&a[i]);
    }   
    shellSort(a, len); // 调用希尔排序函数
    printf("希尔升序排列后结果为:\n");
    for (i = 0; i < len; i++) { // 排序后的结果的输出
        printf("%d\t",a[i]);
    }
    printf("\n");

    return 0;
}



四、选择排序

0.直接选择

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

void Selection_Sort(int Arr[])
	for (int i = 0; i < BUFFSIZE - 1; i++)
		for (int j = i + 1; j < BUFFSIZE; j++)
			if (Arr[i] < Arr[j])  // 将大的元素移到前面
			{
				int tmp = Arr[i];
				Arr[i] = Arr[j];
				Arr[j] = tmp;
			}
    //输出排序后的元素
	for (int i = 0; i < BUFFSIZE; i++)
		cout << Arr[i] << " ";
	cout << endl;
}

1.堆排序

一个序列,如果将序列看成一个完全二叉树,非终端结点的值均小于或大于左右子结点的值。

利用树的结构特征来描述堆,所以树只是作为堆的描述工具,堆实际是存放在线形空间中的。

  • 首先堆是一颗完全二叉树
  • 其次堆中存储的值是偏序

Min-heap(小根堆): 父节点的值小于或等于子节点的值

Max-heap(大根堆): 父节点的值大于或等于子节点的值

在这里插入图片描述
基本思路
将无序序列建成一个堆
输出堆顶的最小(大)值
使剩余的n-1个元素又调整成一个堆,则可得到n个元素的次小值
重复执行,得到一个有序序列

堆的重新调整

输出堆顶元素后,以堆中最后一个元素替代之
将根结点与左、右子树根结点比较,并与小者交换
重复直至叶子结点,得到新的堆

时间效率:O(nlogn)O(nlogn)
空间效率:O(1)O(1)
稳 定 性:不稳定
适用于 nn 较大的情况

代码:

int heap[N],sz=0;
void push(int x)
{
    int i=sz++;
    while(i>0)//往上走
    {
        //父结点的编号
        int p=(i-1)/2;
        //如果不需要再交换就break;
        if(heap[p]<=x)break;
        heap[i]=head[p];
        i=p;
    }
    heap[i]=x;
}
//删除最小值:先把最小值丢掉,先把最后一个节点的值放到根节点处,然后排序交换即可
int pop()
{
    //最小值
    int ret=heap[0];
    int x=heap[--sz];
    int i=0;
    while(i*2+1<sz)//因为堆是完全二叉树偏左嘛
    {
        //左右儿子
        int a=i*2+1,b=i*2+2;
        //选出儿子中最小的
        if(b<sz&&heap[b]<heap[a])a=b;
        //如果不需要交换就break
        if(heap[a]>=x)break;
        //交换
        heap[i]=heap[a];
        i=a;
    }
    heap[i]=x;
    return ret;//返回被丢掉的那个最小值
}


2.二叉堆

二叉堆是一种支持插入、删除、查询最值的数据结构,是一棵满足堆性质的完全二叉树,树上的每一个节点都带有一个权值。

大根堆:
树上任意一个节点的权值都小于等于其父节点的权值。

小根堆:
树上任意一个节点的权值都大于等于其父节点的权值。

二叉堆的储存可以采用层次序列的储存方式,直接用一个数组保存:按从左到右,从上到下的顺序依次为二叉堆上的节点编号,如果根节点的编号为1的话,每个节点的左子节点的编号为根节点编号 2*\ 2,右子节点的编号为根结点编号 2+1* \ 2 + 1,每个节点的根节点的编号为自身编号 / 2。

以大根堆为例讨论二叉堆的常见操作:

二叉堆的插入操作:

将新插入的值放在储存二叉堆的数组的末尾,然后按照二叉堆的规则向上交换,直到满足二叉堆的性质,时间复杂度为二叉堆的深度,即:Θ(logN)\Theta(logN)

返回堆顶值:
大根堆堆顶的值为堆中的最大值,小根堆堆顶的值为堆中的最小值。

移除堆顶的值:
首先,将堆顶的值与数组末尾的节点交换,之后移除数组末尾的节点(在下面的样例中,移除节点通过记录节点个数的n−1n-1n−1来实现);然后,将新的堆顶的值通过交换的方式向下调整,直至满足二叉堆的性质。

删除任意一个元素:
与删除对顶元素类似,将要删除的元素与数组末尾的元素交换,时候数组长度-1,然后分别检查是否需要向上或者向下调整,时间复杂度为Θ(logN)\Theta(logN)

二叉树的实现可以手写,也可以使用STL

3.手写二叉堆代码

int heap[MAX], n;

void up(int pos) // 向上调整
{
    while (pos > 1)
    {
        if (heap[pos] > heap[pos / 2])
        {
            swap(heap[pos], heap[pos / 2]);
            pos /= 2;
        }
        else
            break;
    }
}

void insert(int val) // 插入节点
{
    heap[++n] = val;
    up(n);
}

int top() // 返回堆顶元素
{
    return heap[1];
}

void down(int pos) // 向下调整
{
    int son = pos * 2;
    while (son <= n)
    {
        if (son < n && heap[son] <= heap[son + 1])
            son++; // 最大的子节点

        if (heap[pos] < heap[son])
        {
            swap(heap[pos], heap[son]);
            pos = son;
            son = pos * 2;
        }
        else
            break;
    }
}

void pop() // 弹出堆顶元素
{
    heap[1] = heap[n];
    n--;
    down(1);
}

void remove(int pos) // 删除指定位置的元素
{
    heap[pos] = heap[n];
    n--;
    up(pos);
    down(pos);
}


五、排序算法对比

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章