数据结构–排序算法


排序算法

本文贴出了关于排序算法中的几个经典算法,重点了解快排和堆排; 本文排序的测试用例为:[49, 38, 65, 97, 76, 13, 27, 49(a)], 其中的 49(a) 表示数组中的重复元素,说明算法是否是稳定的排序算法。 本文贴出的都是代码,详尽的原理和功能在注释中做出了简单说明;

插入排序

直接插入排序

void straight_insert(vector<int> nums)
{
    /*
    *  功能:直接插入排序
    *  原理:假定数组的前面部分已经有序,检测当前索引插入的位置 j;
    *        插入位置后的元素一致后移
    *  说明: O(n^2) Time and O(1) Space, 稳定的排序算法,
    *        为了不影响其他排序算法,参数传递时未添加引用
    */
    int privot = nums[0];
    for (int i = 1; i < nums.size(); ++i)
    {
        if (nums[i] < nums[i - 1])
        {
            privot = nums[i]; // 设置哨兵,作为临时空间
            // 从右往左搜索, 找到第一个小于 privot 元素的位置
            int j = i - 1;
            while (j >= 0 && privot < nums[j])
            {
                nums[j + 1] = nums[j];
                --j;
            }
            nums[j + 1] = privot; // Insert
        }
    }
    cout << "Straight Insert Result: ";
    print(nums);
}

Shell 排序

希尔排序的过程

void shell_sort(vector<int> nums)
{
    /*
    * 功能:Shell 排序
    * 原理:Shell 排序是基于直接插入排序的算法,
    *       将数组元素按增量划分为一组,
    *       如 delta = 4,则 位置为[0,4,8,...]为一组,位置为[1,5,9,...]的元素作为一组
    *       组内元素做直接插入排序;
    *       增量设置为[n/2, n/4, n/8, ... ,1]
    * 说明:Shell 排序的时间复杂度为小于 O(N^2),由于增量的选择,Shell 排序是不稳定排序
    *       为了不影响其他排序算法,参数传递时未添加引用
    */
    int delta = nums.size() / 2;
    while (delta >= 1)
    {
        _shell_insert(nums, delta);
        delta /= 2;
    }
    cout << "Shell Insert Result: ";
    print(nums);
}

基于直接插入排排序的增量排序过程

void _shell_insert(vector<int> &nums, int delta)
{
    /*
    * 功能:Shell 排序的基于增量的直接插入排序
    * 原理:每隔增量的元素作为一组,进行直接插入排序
    */
    int privot = nums[0];
    for (int i = delta; i < nums.size(); ++i)
    {
        if (nums[i] < nums[i - delta])
        {
            privot = nums[i];
            int j = i - delta;
            while (j >= 0 && nums[i] < nums[j])
            {
                nums[j + delta] = nums[j];
                j -= delta;
            }
            nums[j + delta] = privot;
        }
    }
}

选择排序

简单选择排序

void simple_select_sort(vector<int> nums)
{
    /*
    * 功能:简单选择排序
    * 原理:数组的左面表示有序部分,每一趟循环完成,确定其余元素的最大(最小)值,并纳入有序部分
    * 说明: Time O(N^2), 因为存在跨元素交换,所以是不稳定的排序算法
    *       为了不影响其他排序算法,参数传递未添加引用
    */
    int position = 0;
    for (int i = 0; i < nums.size() - 1; ++i)
    {
        position = i;
        for (int j = i+1; j < nums.size(); ++j)
        {
            if (nums[position] > nums[j])
                position = j;
        }
        swap(nums[i], nums[position]);
    }
    cout << "Simple Select Result: ";
    print(nums);
}

###堆排序

堆排序的主过程

void heaq_sort(vector<int> nums)
{
    /*
    * 功能:堆排序算法
    * 原理:利用数组可以模拟完全二叉树的原理,使得父节点与子节点存在对应联系
    *       堆排序是对简单选择排序的优化,认为数组的右边为有序序列
    *       通过循环以下过程,得到有序数组
    *       1.建立大根(小根)堆,找出最大(最小)值,优化了简单排序的选择过程
    *       2.将得到的最值与无效序列的最后一个值互换
    *       1,2 过程循环,即可得到有序数组
    * 说明:由于需要建立根堆这个特性,使得堆排序用于筛选数组最大(最小)n 个元素
    *       Time O(N*logN),与简单选择相同,存在跨元素交换,所以是不稳定排序
    *       为了不影响其他排序算法,参数传递未添加引用
    *
    */
    for (int i = 0; i < nums.size(); ++i)
    {
        _build_heaq(nums, nums.size() - i);
        swap(nums[0], nums[nums.size()-i-1]);
    }
    cout << "Heaq Sort Result: ";
    print(nums);
}

建立堆栈的过程

void _build_heaq(vector<int> &nums, int length)
{
    /*
    * 功能:建立根堆 
    * 原理:建堆的过程是从下往上的,首先需要找到第一个存在子节点的节点
    *       每找到一个节点,则已此结点为根的树,需要调整元素,完成建堆过程
    */
    int i = (nums.size() - 1) / 2;
    while (i >= 0)
    {
        _heaq_adjust(nums, i, length);
        --i;
    }
}

堆栈节点的调整实现, 使得以该节点为根的树符合根堆特征

void _heaq_adjust(vector<int> &nums, int curr,int length)
{
    /*
    * 功能:堆调整,使得以该节点为根的树,都符合大根堆特征
    * 原理;比较当前节点与其较大子节点的大小,如果大于,不用调整
    *       如果小于,则交换,并将该子节点设为需要调整的节点
    */
    int child;
    while (curr < length)
    {
        child = 2 * curr + 1;
        if (child < length)
        {
            if (child + 1 < length && nums[child] < nums[child + 1])
                ++child;
            if (nums[curr] < nums[child])
            {
                swap(nums[curr], nums[child]);
                curr = child;
            }
            else
                break;
        }
        else
            break;
    }
}


交换排序

###冒泡排序

void bubble_sort(vector<int> nums)
{
    /*
    * 功能:冒泡排序算法
    * 原理:相邻元素之间两两比较,交换;当每次比较整个数组时,最大(最小元素)被交换到指定位置
    * 说明:冒泡排序的时间复杂度为 O(N^2), 因为是相邻元素比较,所以是稳定的排序算法
    */
    for (int i = 0; i < nums.size()-1; ++i)
    {//冒泡排序的次数
        for (int j = 1; j < nums.size() - i; ++j)
        {//将除已确定的元素进行冒泡排序
            if (nums[j] < nums[j - 1])
            {
                swap(nums[j], nums[j-1]);
            }
        }
    }
    cout << "Bubble Sort Result: ";
    print(nums);
}

###快速排序

快排的主过程

void quick_sort(vector<int> &nums, int low, int high) 
{
    /*
    * 功能:快排算法
    * 原理:快排运用的是分治法的原理
    *       1. 将数组中的元素划分为大于等于 key 和小于 key 的部分
    *       2. key 左右两边再进行快速排序,
    *       3. 经过多次的快排使得各个部分有序,则整体有序
    * 说明:Time O(NlogN), 因为选择的 key 都是第一个或者最后一个元素,所以是不稳定的算法
    */
    if (low < high)
    {
        int key_position = _quick_sort_swap(nums, low, high);
        quick_sort(nums, 0, key_position - 1);
        quick_sort(nums, key_position + 1, high);
    }
}

通过交换,将数组划分为大于等于 Key 和小于等于 key 的过程

int _quick_sort_swap(vector<int> &nums, int low, int high)
{
    /*
    * 功能;利用交换的方法,将数组划分为大于等于key和小于key两个部分,并返回key位置
    * 原理:利用 Two pointers 方法,第一个元素或者最后一个元素作为 key
    */
    int key = nums[low];
    while (low < high)
    {
        while (low < high && nums[high] >= key)
            --high;
        swap(nums[high], nums[low]);
        while (low < high && nums[low] < key)
            ++low;
        swap(nums[high], nums[low]);
    }
    return low;
}

##归并排序

归并排序的主过程

void merge_sort(vector<int> nums)
{
    /*
    * 功能:归并排序
    * 原理:将有序的序列合并,将数组划分为多个有序的序列,然后合并
    *       1. 只有一个元素的序列是有序的;
    *       2. 将一个元素的序列合并,则数组会被划分为多个元素数目为 2 的序列;
    *       3. 直到元素数目为数组大小的序列,该序列中数组有序;
    * 说明:Time O(NlogN), Space O(N);都是相邻的元素的比较,所以是稳定的排序算法
    */
    vector<int> temp(nums.begin(), nums.end());
    int group_len = 1;
    int double_group_len;
    while (group_len <= nums.size())
    {
        int i = 0;
        double_group_len = 2 * group_len;
        while (i + double_group_len <= nums.size())
        {
            _merge(nums, temp, i, i + group_len - 1, i + double_group_len - 1);
            i += double_group_len;
        }
        if (i + group_len <= nums.size())
        {
            _merge(nums, temp,i, i+group_len-1, nums.size() - 1);
        }

        nums = temp;
        group_len = double_group_len;
    }
    cout << "Merge Sort Result: ";
    print(nums);  
}

合并两个有序序列的过程

void _merge(vector<int> &nums, vector<int> &temp, int first_start, int first_end, int second_end)
{
    /*
    * 功能: 将两个有序的序列合并
    */
    int second_start = first_end + 1;
    int index = first_start;
    int i, j;
    for (i = first_start, j = second_start; i <= first_end && j <=second_end;)
    {
        if (nums[i] < nums[j])
            temp[index++] = nums[i++];
        else
            temp[index++] = nums[j++];
    }
    while (i <= first_end) temp[index++] = nums[i++];
    while (j <= second_end) temp[index++] = nums[j++];
}