8. 排序总结

一. 时空复杂度

转自:http://blog.chinaunix.net/uid-21457204-id-3060260.html

二、实现代码

http://blog.csdn.net/touch_2011/article/details/6767673  漫谈经典排序算法  对《数据结构》上的算法总结得很好,主要对着实现了一遍。

与书一致,为了叙述方便,以下源代码中均不考虑A[0],默认下标从1开始。

//-------------插播一段交换int a, b两个数据值的方法------------------------------------

方法一:使用异或^操作符。

void swap(int &a, int &b)

{

    a = a^b;

    b = a^b;

    a = a^b;

}

方法二:使用加法

   a = a+b; // 可能溢出,方法一则不会,且位操作要比加减法 操作要快些

   b = a - b;

   a = a -b;

//-------------end--------------------------------------

  (1) 插入排序、希尔 排序  --- 基于插入思想

// 直接插入排序
// 最好 O(n), 最差 O(n^2), 平均 O(n^2), 稳定
void Sort::insertSort(int* a, int n)
{
    if(a == NULL || n < 1) return;
    for(int i = 2; i <= n; i++)
    {
        a[0] = a[i]; // 哨兵
        int j;
        for(j = i - 1; j > 0; j--)
        {
            if(a[0] < a[j]) // move back
            {
                a[j+1] = a[j];
            }
            else
                break;
        }
        a[j+1] = a[0];
    }
}

int Sort::binarySearch(int *a, int start, int end, int value)
{
    if(start > end) // 没找到
        return start;
    int mid = (start + end) / 2;
    if(a[mid] == value)
        return mid;
    else if( a[mid] < value)
        return binarySearch(a, mid + 1, end, value);
    else
        return binarySearch(a, start, mid -1, value);
}
// 二分查找插入排序
// 最好 O(nlogn), 最差 O(n^2), 平均 O(n^2), 非稳定
// 只在数据量较大,且 比较无序的 情况下 优于 直接插入排序
void Sort::binaryInsertSort(int *a, int n)
{
    if(a == NULL || n < 1) return;
    int pos;
    for(int i = 2; i <= n; i++)
    {
        a[0] = a[i];
        pos = binarySearch(a, 1, i - 1, a[0]);
        for(int j = i - 1; j >= pos; j--)
            a[j+1] = a[j];
        a[pos] = a[0];
    }
}

// 表插入排序
// 最好 O(n), 最差 O(n^2), 平均 O(n^2), 稳定
// 只是用改变存储结构,用改变连接关系代替实际移动,比较次数
// 没有变化; 且无法随机访问,比较无用。(用双向链表, 循环数组会好一点)
void Sort::tableInsertSort(PNode list, int n)
{
    int p, ahead; // head 插入位置的前一个结点
    int i;
    // 初始化
    list[0].next = 1;
    list[1].next = 0;

    // 逐个插入
    for(i = 2; i <= n; i++)
    {
        ahead = 0;
        p = list[0].next; // 从表头遍历
        // 遍历链表,寻找插入位置
        while(p != 0 && list[p].key <= list[i].key)
        {
            ahead = p;
            p = list[p].next;
        }
        list[ahead].next = i;
        list[i].next = p;
    }

    arrange(list, n);
}
// 将表中元素按增序重排到a[1,n]
void Sort::arrange(PNode list, int n)
{
    int p = list[0].next;
    int q;
    Node tmp;
    for(int i = 1; i < n; i++)
    {
        while(p < i) p = list[p].next; // [1, i-1] 有序,得到原来在[1, i-1]间元素的新位置
        q = list[p].next;
        if(p != i) // 交换到当前位置i
        {
            tmp.key = list[p].key;
            tmp.next = list[p].next;
            list[p] = list[i];
            list[i] = tmp;
            list[i].next = p; // 指回到原来的位置p
        }
        p = q; // 下一个 元素i+1的位置
    }
    cout << "tableInsertSort" << endl;
   for(int i = 1; i <= n; i++)
   {
        cout << list[i].key << " ";
    }
    cout << endl;
}
// 希尔排序
// 与增量递减序列的取值有关, 基本 O(n^2),  不稳定
void Sort::shellSort(int* a, int n)
{
    int shellTable[] = {5, 3, 1};// 增量递减序列(序列中的值互质),末尾元素为1
    for(int i = 0; i < sizeof(shellTable) / sizeof(int); i++)
    {
        shellInsertSort(a, n, shellTable[i]);
    }
}
// 希尔排序中所采用的插入排序,元素间隔为dk
void Sort::shellInsertSort(int* a, int n, int dk)
{
    if(dk >n)
        throw std::exception("invalid input");
    for(int t = 1; t <= dk; t++)
    {
        for(int i = dk + t; i <= n; i+= dk)
        {
            a[0] = a[i]; // 哨兵暂存
            int j;
            for(j = i - dk; j > 0; j -= dk)
            {
                if(a[0] < a[j])
                    a[j + dk] = a[j];
                else
                    break;
            }
            a[j+dk] = a[0];
        }
    }
}

 (2)  简单选择排序,堆排序--- 基于选择思想

// 简单选择排序
// 最好、最差、平均 都是 O(n^2) 稳定
void Sort::selectSort(int* a, int n)
{
    int index;
    for(int i = 1; i < n; i++) // 进行n-1次,每次选出最小的
    {
        index = i;
        int j;
        for(j = i + 1;  j <= n; j++)
        {
            if(a[index] > a[j])
            {
                index = j; // 记录最小的下标为index
            }
        }
        if(index != i) // 交换
        {
            swap(a[index], a[i]);
        }
    }
}

// 递归选择排序
// 最好、最差、平均 都是 O(n^2) 稳定
void Sort::recursionSelectSort(int* a, int n)
{
    if(n == 1) // 退出
        return ; 
    // 1. 选择最小的元素
    int index = 1;
    for(int i = 1; i <= n; i++)
    {
        if(a[index] > a[i])
            index = i;
    }
    // 2. 交换 1 和index
    if(index != 1)
    {
        swap(a[index], a[1]);
    }
    // 3. 子序列[a+i, a+n]递归
    recursionSelectSort(a + 1, n - 1);
}

// 堆调整  a[i, n]为除 i 外,其他符合堆性质的序列,本质上是使用堆结构,逐次从中选择最大的元素,是选择排序
// 最好、最差、平均 都是 O(nlog(n))  不稳定
void Sort::heapAdjust(int* a, int i, int n) // 调整堆,保持大顶堆的性质,参数i指向根结点 
{
    int tmp = a[i];
    int left, right, largest; // 记录 根左孩子右孩子 这三者中的最大者index
    largest = left = i * 2;
    if(left > n) return;
    right = left + 1;
    if(right <= n && a[right] > a[left]) largest = right;
    if(a[largest] > a[i]) // 根结点小于子结点的最大值,需交换根与最大子结点
    {
     swap(a[largest], a[i]);
heapAdjust(a, largest, n); // 调整子堆 } } // 创建堆, 即对[n/2, n]进行调整,得到大顶堆 void Sort::createHeap(int* a, int n) { // n/2表示,由n个元素构建的完全二叉树,最后一个非叶子结点的下标为n/2(取下整) // 若取大于 n/2的结点,都是叶子结点不用adjust for(int i = n/2; i >= 1; i--) { heapAdjust(a, i, n); } } // 堆排序 void Sort::heapSort(int* a, int n) { createHeap(a, n); // 构建大顶堆 // 排序,每次选最大的数 a[1](大顶堆的根结点)交换放到序列最后一个位置上 for(int i = n; i >= 2; i--) // 元素数量 { // 堆顶元素 和 最后一个位置 交换 swap(a[1], a[i]); heapAdjust(a, 1, i - 1); } }

 (3) 冒泡排序, 快速排序--- 基于交换思想

// 冒泡排序
// 最好、最差、平均 都是 O(n^2) 稳定
void Sort::bubbleSort(int* a, int n)
{
    for(int i = 1; i < n; i++)
        for(int j = 1; j < n - i + 1; j++)
        {
            if(a[j + 1] < a[j]) // swap
            {
   swap(a[j+1], a[j]);
}
// 快速排序 不稳定
// 最好(递归树,每次划分都对称)的复杂O(nlog(n)), 最坏时(每次划分都不对称,如顺序和逆序时,退化为冒泡)O(n^2), 平均O(nlogn)
void Sort::quickSort(int* a, int low, int high)
{
    if(low < high)
    {
        int position = partition(a, low, high);
        quickSort(a, low , position - 1);
        quickSort(a, position + 1, high);
    }
}

int Sort::partition(int* a, int low, int high)
{
    int pivotkey = a[low]; // 枢轴
    // 注意:low < high 的条件很重要
    while(low < high)
    {
        while(low < high && a[high] >= pivotkey) high--;
        a[low] = a[high]; // low=high 或 a[high] < pivotkey 时,将小于枢轴值放于低位
        while(low < high && a[low] <= pivotkey) low++;
        a[high] = a[low]; // 放于高位
    }
    // 结束时,有 low = high 为枢轴的下标
    a[low] = pivotkey; // 赋值 枢轴
    return low; // 返回枢轴下标
}

// 非递归版
void Sort::quickSortStack(int* a, int low, int high) 
{
    if(low < high)
    {
        std::stack<int> callStackLow;
std::stack<int> callStackHigh;
     callStackLow.push(low);
callStackHigh.push(high);
while(!callStackLow.empty()) { int curLow = callStackLow.top(); int curHigh = callStackHigh.top(); callStack.pop(); // 求 partition if(curLow < curHigh) { int position = partition(a, curLow, curHigh); callStackLow.push(curLow);
callStackHigh.push(position
- 1);
callStackLow.push(position+1); // 二者无顺序性 callStackHigh.push(j); } } } }

 (4) 归并排序

//----------------------基于分制思想----------------------------------------
// 归并排序 递归,先自顶向下分解,再由递归退栈,自底向上合并
// 最好、最坏、平均 复杂度 都为 O(nlog(n)) (递归树)
void Sort::mergeSort(int* a, int start, int end)
{
    if(start < end)
    {
        int mid = (start + end) / 2 ; // 分成两部分
        // 先分解
        mergeSort(a, start, mid);
        mergeSort(a, mid + 1, end);
        // 再合并
        merge(a, start, mid, end);
    }
}

//将有序的src[start, mid] 和 src[mid+1,..n] 归并为有序的 src[start, end]
void Sort::merge(int* a, int start, int mid, int end)
{
    int i, j, k;
    // 申请辅助数组
    int length1 = mid - start + 1;
    int length2 = end - mid;
    int* array1 = new int[length1]; //
    int* array2 = new int[length2]; //
    // 把内容copy到辅助数组中
    std::memcpy(array1, a + start, sizeof(int) * length1);
    std::memcpy(array2, a + mid + 1, sizeof(int) * length2);
    // 归并
    for(i = 0, j = 0, k = start; i < length1 && j < length2;)
    {
        if(array1[i] <= array2[j]) 
            a[k++] = array1[i++];
        else
            a[k++] = array2[j++];
    }
    if(i < length1)
        std::memcpy(a + k, array1 + i, sizeof(int) * (length1 - i)); // 复制剩余的src[start, mid] 到 tr
    if(j < length2)
        std::memcpy(a + k, array2 + j, sizeof(int) * (length2 - j)); // 复制剩余的src[mid+1, end] 到 tr
    delete[] array1;
    delete[] array2;
}
// 归并排序 非递归 ---直接自底向上合并
void Sort::mergeSortNoRecusion(int* a ,int n)
{
    int step = 1, i; // 要合并的组长度为step
   
    while(step < n)
    {
        i = 1;
        while(i + 2 * step - 1 <= n) // 查看是否可以合并 a[i,i+step-1] , a[i+step, i+2*step-1]
        {
            merge(a, i, i + step - 1, i + 2 * step -1);
            i = i + 2 * step; // 下一组
        }
        // 处理尾部剩余部分
        if(i < n)
        {
            merge(a, i, i + step/2 -1, n); //有序数列 a[i, i+step/2-1], a[i+step/2, n] 合并
        }
        step *= 2; // 2倍增长
    }
}

(5) 计数排序、基数排序、桶排序

/------------------------线性时间排序--------------------------------------
// 计数排序,要求a中的元素都为非负整数
// 最快、最差、平均 时间复杂度 o(n+k), n为元素个数,k为a中的最大值。需辅助空间 n+k 稳定
void Sort::countingSort(int* a, int n)
{
    if(a == NULL || n < 1) return;
   // 找到最大的元素
    int maxValue = a[1];
    for(int i = 2; i <= n; i++)
    {
        if(maxValue < a[i])
            maxValue = a[i];
    }
    // 初始化计数数组 count 
    int* count = new int[maxValue + 1];
    memset(count, 0, (maxValue + 1) * sizeof(int)); 
    // 计算等于 a[i] 的记录个数
    for(int i = 1; i <= n; i++)
    {
        count[a[i]]++;
    }
    // 计算 小于等于 a[i] 的记录个数,确定其顺序
    for(int i = 1; i <= maxValue; i++)
    {
        count[i] += count[i-1];
    }
    // 从后往前扫描a数组(为了保持稳定性),把各个元素放在临时数组对应的位置上
    int* tmp = new int[n+1];
    for(int i = n; i > 0; i--)
    {
        tmp[count[a[i]]] = a[i];
        count[a[i]]--;
    }
    // 把有序数组的元素拷贝到a中
    memcpy(a, tmp, (n+1) * sizeof(int));
    delete[] count;
    delete[] tmp;
}

// 简单整数基数排序, 每一位都用一次计数排序
// 最坏、最好、平均都是 O(n), 稳定排序, 辅助空间k+n
void Sort::radixSort(int* a, int n)
{
    int i;
    int maxValue = a[1]; // 统计最大数
    for(i = 2; i <= n; i++)
    {
        if(maxValue < a[i])
            maxValue = a[i]; // 最大值
    }
    int d = 0; //统计最大数的位数
    if(maxValue > 0)
        d = 1;
    while(maxValue / 10 != 0)
    {
        d++;
        maxValue/=10;
    }
    for(int i = 1; i <= d; i++)
        countingSort(a, n, 9, i); // 从最低位开始排序
}
 // 计数排序, n为数组a的记录个数,radix为记录中最大值,按第d为排序
void Sort::countingSort(int* a, int n, int radix ,int d) // radix表示基数的取值最大值,十进制默认为9. [0,9]
{
   int* count = new int[radix + 1];
   int* b = new int[n + 1];
   memset(count, 0, (radix + 1) * sizeof(int));
   // a[i] 在 d 位 的值为 a[i] / (int)(pow(10, d-1) % 10, 在count中记录其出现的个数
   for(int i = 1; i <= n; i++)
   {
       int v = a[i]/(int)(std::pow(10.0, d-1)) % 10;
       count[v]++;
   }
   // 统计 小于等于 该值的个数
   for(int i = 1; i <= radix; i++)
   {
       count[i] += count[i-1];
   }
   // 从后往前 扫描数组,把各原始放在有序序列中相应的位置上
   for(int i = n; i > 0; i--)
   {
       int v = a[i]/(int)std::pow(10.0, d-1) % 10;
       b[count[v]] = a[i];
       count[v]--;
   }
   // 排序结果 拷回 a中
   memcpy(a, b, (n+1) * sizeof(int));
   delete[] count;
   delete[] b;
}

// 桶排序,感觉和基数排序挺相似,不过基数排序的radix为每位的取值范围,有多位,
// 而桶则是直接根据取值范围进行等间隔划分
// 复杂度为O(n + k), k为桶的个数 最好情况(数均匀分布于各桶中) O(n), 最坏情况O(n^2), 
void Sort::bucketSort(int* a, int n, int k)
{
    // 建立链表 (也可以用 k * n 的二维数组,但比较耗费空间)
    // 链表结点描述
    typedef struct Node
    {
        double key;
        struct Node* next;
    };
    // 辅助数组元素描述
    typedef struct Head
    {
        Node* next;
    };
    int i, j;
    Node* p;
    Node* q;
    Node* node;
    // 求出最大、最小值,对桶范围进行划分
    int maxValue = a[1];
    int minValue = a[1];
    for(int i = 2; i <= n; i++)
    {
        if(maxValue < a[i]) maxValue = a[i];
        if(minValue > a[i]) minValue = a[i];
    }
    Head* heads = new Head[k];
    for(int i = 0; i < k; i++)
        heads[i].next = NULL;
   
    for(int i = 1; i <= n; i++)
    {
        node = new Node();
        node->key = a[i];
        node->next = NULL;
        // 判断a[i]该进入哪个桶
        int index;
        if(a[i] == maxValue)
            index = k - 1;
        else
            index = (double)(a[i] - minValue) / (maxValue - minValue) * k;
        p = q = heads[index].next;
        if(p == NULL) // 该桶中尚未有元素
        {
            heads[index].next = node;
            continue;
        }
        else
        {
            // 对已有元素进行排序(也可以全部分配完在排序)
            // 这里直接使用进行插入排序
            while(p)
            {
                if(a[i] < p->key)
                    break;
                q = p; // 记录前一个结点
                p = p->next;
            }
            if(p == NULL)
                q->next = node; // 插到尾部
            else
            {
                node->next = p;
                q->next = node;
            }
        }
        
    }
    int t = 1;
    // 将排序后的结果拷到a中
    for(int i = 0; i < k; i++)
    {
        p = heads[i].next;
        while(p)
        {
            a[t++] = p->key;
            q = p;
            p = p->next;
            delete q; // 删除链表
        } 
    }
    delete[] heads; 

}

 鸽巢原理及应用

http://blog.csdn.net/xuhx/article/details/5928079 很有意思的一道题。

原文地址:https://www.cnblogs.com/wenshanzh/p/3298886.html