【数据结构与算法(九)】——查找与排序

打卡第九天
快速排序==》 C++异常处理没有搞定(lll¬ω¬)
排序数组本身就是数组旋转的一个特例。
在对数组进行排序或查找时,要注意数组中有相同数字的特例,比如下面的旋转数组的时候

查找与排序

查找:顺序查找、二分查找、哈希表查找、二叉排序查找

  • 二分查找:要求在排序的数组(或部分排序的数组)中查找一个数字或统计某个数字出现的次数
  • 哈希表的优点是利用它能够在O(1)时间内查找某一个元素,是效率最高的查找方式;其缺点是需要额外的空间来实现哈希表
  • 二叉排序查找算法对应的数据结构是二叉排序树

排序:插入排序、冒泡排序、归并排序、快速排序

题目

实现快速排序算法

实现快速排序算法的关键在于先在数组中选择一个数字,接下来把数组中的数字分为两部分,比选择的数字小的移到数组的左边,比选择的数字大的移到数组的右边

思路:

这里写图片描述
突然就卡在快速排序了,自己都有点懵了,因为之前用python实现的时候是使用了一个新的数组,这一次要在原来的数组里面不使用新的空间进行划分,直接看图吧
选择的用于进行标志的是每个数组的最后一个数data[end]=5。第一轮for循环,small=-1,因为4<5,所以这一轮的small可以加1,small表示的是最新的比flag小的数的位置,又因为small已经指向当前的位置了,所以就不用进行交换了;第二轮同理;第三轮的时候,由于8>5,所以small不用改变,最新的small还是上一轮的3;第四轮的时候,1<5,所以这一轮的small应该指向的是1,所以small+1,之后还要把small现在指向的位置的数的值和1进行交换;后面以此类推……

//快速排序算法
//交换两个数的值
void Swap(int *x, int *y)
{
    int p;
    p = *x;
    *x = *y;
    *y = p;
}
int  Partition(int data[], int length, int start, int end)
{
    if (data == nullptr || start < 0 || end >= length || length <= 0)
        throw new std::exception("不合格的输入!");

    int small = start - 1;  //指向最小的数
    int flag = data[end];       //用于进行划分的标志
    for (int i = 0; i < length; i++)
    {
        if (data[i] < flag) {
            small++;
            Swap(&data[i], &data[small]);
        }
    }
    //最后还要将最后一个数放到前面,和其他小的数放到一起
    ++small;
    Swap(&flag, &data[small]);

    return small;   //返回的是最后一个较小的数的位置,即大数与小数划分处
}

void QuickSort(int data[], int length, int start, int end)
{
    if (start == end)
        return; //只有一个数不需要排序

    int index = Partition(data, length, start, end);
    if (index > start)//如果存在小的序列
        QuickSort(data, length, start, index - 1);
    if (index < end)//如果存在大数的序列
        QuickSort(data, length, index + 1, end);
}

不是很懂c++的异常处理问题,这个代码存在问题,关于异常处理的,所以没有用测试用例运行。。。之后要是弄懂了会回来看的
如下图,奇怪的是我这不是对这个异常进行处理了吗?恳请各位路过的朋友能解个疑[/抱拳]【问题说明:这是在函数里面try-catch的,之后有试过在调用该函数的地方try-catch也不行】
这里写图片描述

给员工年龄进行排序

为员工年龄进行排序,要求时间复杂度在O(n)内,允许使用常数范围内辅助空间,不得大于O(n)

思路:

使用统计的方法,先对员工的年龄进行统计,用一个数组存储某一年龄出现的次数times,之后在返回的ages数组中填入times次该年龄出现的次数。

//给员工年龄进行排序
void SortAges(int ages[], int length)
{
    if (ages == nullptr || length == 0)
        return;
    const int oldestAge = 99;
    int timeOfAges[100];
    //对传进来的年龄进行统计
    for (int i = 0; i < length; i++) {
        int age = ages[i];
        if (age < 0 || age>99)
            throw new std::exception("年龄超出统计范围了!");
        ++timeOfAges[age];
    }
    int index = 0;
    for (int i = 0; i <= oldestAge; i++)
    {
        for (int j = 0; j < timeOfAges[i]; j++)
        {
            ages[index] = i;
            index++;
        }
    }
}

旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1.

思路:

1、旋转之后的数组实际上可以划分为两个排序的子数组,而且前面子数组的元素都大于或等于后面子数组的元素。最小的元素刚好是两个子数组的分界线。这道题可以说是部分排序的,在一开始就有提到部分排序的数组的查找可以使用二分查找法
2、第一个元素大于或等于最后一个元素。找到中间的元素,如果该中间元素位于前面的递增子数组,那么它应该大于或等于第一个指针指向的元素。此时数组中最小的元素应该在该中间元素的后面。可以把第一个指针指向该中间元素,缩小查找范围。移动后第一个指针仍然指向第一个子数组的元素。
3、第一个指针总是指向第一个子数组的元素,第二个指针总是指向第二个子数组的元素。最终第一个指针会指向第一个子数组的最后一个元素,第二个指针会指向第二个子数组的第一个元素。此时,第二个指针指向的正好是最小的元素,这就是循环结束的条件

int Min(int array[], int length)
{
    if (array == nullptr || length <= 0)
        throw new exception("不合法输入!");

    int index1 = 0;
    int index2 = length - 1;
    //为什么是初始化为index1?而不是index2?
    //因为有可能旋转数组旋转的是排序数组的前面0个元素,这样就相当于没有旋转了
    //直接输出第一个元素就好了,因为while循环都不用进去了
    int indexMiddle = index1;
    while (array[index1] >= array[index2]) {
        //循环退出条件,找到最小值的位置了
        if (index2 - index1 == 1) {
            indexMiddle = index2;
            break;
        }
        indexMiddle = (index1 + index2) / 2;
        //如果middle在前面的子数组中
        if (array[indexMiddle] >= array[index1])
            index1 = indexMiddle;
        //如果middle在后面的子数组中,往前移后面的指针,缩小查找范围
        else if (array[indexMiddle] <= array[index2])
            index2 = indexMiddle;
    }
    return array[indexMiddle];
}

一种特殊的情况:数组{1,0,1,1,1}和数组{1,1,1,0,1}都是排序数组{0,1,1,1,1}的旋转数组。在这两数组中,第一个数字、中间的数字、最后一个数字都是1,无法判断中间的1是前面的子数组的还是后面的子数组的。很混乱,比如前面的例子,中间的1是位于后面的子数组;第二个例子,中间的1是位于后面的子数组。这样就不得不采用顺序查找的方法了。

//顺序查找
int MinInOrder(int array[], int index1, int index2)
{
    int result = array[index1];
    for (int i = 0; i <= index2; i++)
    {
        if (array[i] < result)
            result = array[i];
    }
    return result;
}

int MinChoose(int array[], int length)
{
    if (array == nullptr || length <= 0)
        throw new exception("不合法输入!");

    int index1 = 0;
    int index2 = length - 1;
    //为什么是初始化为index1?而不是index2?
    //因为有可能旋转数组旋转的是排序数组的前面0个元素,这样就相当于没有旋转了
    //直接输出第一个元素就好了,因为while循环都不用进去了
    int indexMiddle = index1;
    while (array[index1] >= array[index2]) {
        //循环退出条件,找到最小值的位置了
        if (index2 - index1 == 1) {
            indexMiddle = index2;
            break;
        }
        indexMiddle = (index1 + index2) / 2;
        //修改的地方:
        //如果下标为index1,indexMiddle,index2的三个元素的值都相等,只能用顺序查找
        if (array[index1] == array[index2] && array[index2] == array[indexMiddle])
            return MinInOrder(array, index1, index2);

        //如果middle在前面的子数组中
        if (array[indexMiddle] >= array[index1])
            index1 = indexMiddle;
        //如果middle在后面的子数组中,往前移后面的指针,缩小查找范围
        else if (array[indexMiddle] <= array[index2])
            index2 = indexMiddle;
    }
    return array[indexMiddle];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值