排序算法

排序算法总结:
1、排序的稳定性:对于两个相等的元素,排序后的顺序和排序前一致,该算法是稳定的,反之亦然
2、内排序和外排序:内排序:排序过程中,所有数据都在内存中;数据量大的,需要内外存之间多次交换数据的就是外排序
内排序的性能:时间性能,辅助空间,算法复杂度

#define MAXSIZE 10
typedef struct
{
    int r[MAXSIZE + 1]; // r[0]存放哨兵或者临时变量
    int len;
}SqList;
void swap(SqList *L, int i, int j)
{
    int temp = L->r[i];
    L->r[i]  = L->r[j];
    L->r[j]  = temp;
}

一、冒泡排序 O(n^2)

依次选择序列中的最小或者最大值,将其按从小到大或者从大到小的顺序排列。

void BubbleSort(SqList *L)
{
    int i, j;
    for(i = 1; i < L->len; i++)
    {
        for(j = i; j < L->len - 1; j++)
        {
            if(L->r[j] > L->r[j + 1])
                swap(L, j, j+1);
        }
    }
}
for (i=0; i<n-1; ++i)  //比较n-1轮
    {
        for (j=0; j<n-1-i; ++j)  //每轮比较n-1-i次,
        {
            if (a[j] < a[j+1])
            {
                buf = a[j];
                a[j] = a[j+1];
                a[j+1] = buf;
            }
        }
    }

二、选择排序 O(n^2)

void SelectSort(SqList *L)
{
    int i, j, min;
    for(i = 1; j < L->len; i++)
    {
        min = i;
        for(j = i + 1; j < L->len; j++)
        {
            if(L->r[j] < L->r[min])
                min = j;
        }
        if(i!=min)
            swap(L, i, min);
    }
}

选定一个数,在剩余的序列中选择最小的数与之交换。

三、直接插入排序 O(n^2)

默认前面的序列已经排好序,从后面的序列中依次拿取元素放在前面的序列中合适的位置。 从后面序列拿去的元素依次和前面序列的每一个比它大的元素交换(从后向前),直到遇到比它小的元素为止。

void InsetSort(SqList *L)
{
    for(int i = 1; i < L->len; i++)
    {
        for(int j = i - 1; j >= 0; j--)
        {
            if(L->r[j] > L->r[j+1])
                swap(L, j, j+1);
        }
    }
}

四、希尔排序 O(n^3/2)

从数组首地址开始,每隔gap个元素选择一个元素组成子序列,子序列还是在原数组的相应位置上,对子序列使用插入排序算法排好序;再缩小gap,重复上述操作,直到gap=1,结束排序。

void ShellSort(SqList *L)
{
    for(int gap = L->len / 2; gap >=1; gap/=2) //选择gap
    {
        for(int i = 0; i < gap; i++)  //从第一个元素开始,每隔gap个元素,取一个元素,组成第一个子序列;从第二个元素开始,每隔gap个元素,取一个元素,组成第二个子序列;………………
        {
            for(int j = i - gap; j >= 0; j-=gap)
            {
                if(L->r[j] > L->r[j + gap])
                    swap(L, j, j + gap);
            }
        }
    }
}

希尔排序是跳跃式的取值,相同的两个值可能分不到同一个子序列,导致排序后,顺序和之前不一样,因此,希尔排序不是稳定的排序算法。

五、堆排序

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

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

void HeapSort(SqList *L)
{
    for(int i = L->len / 2 - 1; i >= 0; i--)  //从最后一个非叶子节点开始,构建堆
    {
        HeapAdjust(L, i, l->len);
    }
    for(int j = l->len - 1; j >= 0; j--)
    {
        swap(L, 0, j);
        HeapAdjust(L, 0, j);  //到j的意思是把根节点依次取出
    }
}

void HeapAdjust(SqList *L, int p, int len)
{
    int child = 2*p+1;  //child为p节点的孩子节点
    int temp = L->r[p];
    while(child < len)
    {
        if(child + 1 < len && L->r[child] < L->r[child + 1])  //这个if是比较左右孩子节点的大小,并把大值节点的下标赋值给child
            child++;
        if(temp < L->r[child])
        {  //为了调整堆的时候重用代码,此处不进行直接交换,而是一直迭代到不需要调整,再把根节点的值赋值给这个节点
            L->r[p] =  L->r[child];
            p = child;
            child = 2*p + 1;
        }
        else
            break;
    }
    L->r[p] = temp;
}

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。

六、归并排序

采用经典的分治(divide-and-conquer)策略将问题分成一些小的问题然后递归求解,即分而治之。
排序算法_第1张图片

void MergeSort(int arr[], int low, int high)
{
    if(low < high)
    {
        int mid = (low + high) / 2;
        MergeSort(arr, low, mid);  //high会变化
        MergeSort(arr, mid + 1, high);
        Merge(arr,low,mid,high);
    }
}

void Merge(int arr[], int low, int mid, int high)
{
//low为第1有序区的第1个元素,i指向第1个元素, mid为第1有序区的最后1个元素
    int i = low, j = mid + 1, k = 0;  //mid+1为第2有序区第1个元素,j指向第1个元素
    
    int *temp=new(nothrow) int[high-low+1]; //temp数组暂存合并的有序序列
    if(!temp)   //内存分配失败
    {
        cout<<"error";
        return;
    }
    
    while(i <= mid && j <= high)
    {
        if(arr[i] <= arr[j])  //较小的先存入temp中
            temp[k++] = arr[i++];
        else
            temp[k++] = arr[j++];
    }
    while(i <= mid)  //若比较完之后,第一个有序区仍有剩余,则直接复制到t数组中
        temp[k++] = arr[i++];
    while(j <= high)   //同上
        temp[k++] = arr[j++];
    for(i = low,k = 0;i <= high;i++,k++)//将排好序的存回arr中low到high这区间
		arr[i] = temp[k];
    delete []temp;//删除指针,由于指向的是数组,必须用delete []  
}

非递归实现:

void MergeSort2(int arr[],int n)   //n代表数组中元素个数,即数组最大下标是n-1
{
    int size=1,low,mid,high;
	while(size<=n-1)
	{
		low=0;
		while(low+size<=n-1)
		{
			mid=low+size-1;
			high=mid+size;
			if(high>n-1)  //第二个序列个数不足size
				high=n-1;
			Merge(arr,low,mid,high);  //调用归并子函数
			low=high+1;  //下一次归并时第一关序列的下界
		}
		size*=2;  //范围扩大一倍
	}

}

时间复杂度为O(nlogn),空间复杂度为 O(n),归并排序比较占用内存,但效率高且稳定。

七、快速排序

快速排序:将数组分成两部分,其中一部分的任意值都比另一部分小;重复上述过程,直至排序完成。和冒泡排序一样,属于交换排序类,不同的是快排是跳跃式交换,效率更高,同时也是不稳定的排序算法。

void QuickSort(SqList *L, int low, int high)
{
    int temp;
    while(low < high)
    {
        temp = Partition(L, low, high);
        QuickSort(L, low, temp - 1);
        QuickSort(L, temp + 1, high);
    }
}

int Partition(SqList *L, int low, int high)
{
    int key = L->r[low];  //选择第一个数作为基准,左边的都比右边的小,返回基准的下标
    while(low < high)
    {
        while(low < high && key < L->r[high])
            high--;
        swap(L, low, high);
        while(low < high && l->r[low] < key)
            low++;
        swap(L, low, high);
    }
    return low;
     
}

最优情况下,基准值在序列中间位置,快排时间复杂度O(nlogn);最坏的情况下,待排序序列为逆序,每次划分只得到比上次少一个值的子序列,时间复杂度O(n^2)。平均来看,时间复杂度和空间复杂度均为O(nlogn)。

优化:
1、基准值的选取很重要,尽量选择位于的中间值,三数取中法:先选择low、mid和high三个数排序,将中间值作为基准。
2、优化不必要的交换

int Partition(SqList *L, int low, int high)
{
    int key = L->r[low];  //选择第一个数作为基准,左边的都比右边的小,返回基准的下标
    int temp = key; 
    while(low < high)
    {
        while(low < high && key <= L->r[high])
            high--;
        //swap(L, low, high);
        L->r[low] = L->r[high];
        while(low < high && L->r[low] <= key)
            low++;
        //swap(L, low, high);
        L->r[high] = L->r[low];
    }
    L->r[low] = temp;
    return low;
     
}

3、优化递归

总结:

排序算法_第2张图片

你可能感兴趣的:(排序算法总结)