快速排序分析

来源:互联网 发布:nuts二战知乎 编辑:程序博客网 时间:2024/05/02 00:26


In general, the code you have is neat, and easy to follow. The QuickSort algorithm is relatively traditional, and the pieces I expect to see are about where I expect them to be.

Let's go through the issues that I see though... and some of them are serious...

Namespaces

Using namespace::std is generally a poor idea. The possibility of namespace pollution is real. In fact, you have implemented a swap function and there already is one std::swap, but we'll get to that.

Style

  • You have a variable min, but this should be mid.

  • sometimes you put the function parameters with the left/right indices before the array, and sometimes after. You have:

    void quicksort(int *arr, int left, int right) {

    and you also have:

    void swap(int i,int j, int *a) {

    pick one, and stick with it. I would personally recommend putting the array first, and the indices afterwards.

  • Use whitespace appropriately. Operators like the << operator on cout are operators like any others. Use the space to improve readability:

    cout<<"QS:"<<left<<","<<right<<"\n";

    should be:

    std::cout << "QS:" << left << "," << right << "\n";

Bugs

You have a few bugs in here which should be addressed:

  1. There's the potential for integer overflow when calculating the mid point. This is a 'famous' bug. Your code int min = (left+right)/2; should be done as:

    int mid = left + (right - left) / 2;

    The above solution will not overflow.

  2. You should, when partitioning the data, consider values equal to the pivot value, to be either left or right of the pivot. Your code you use a strict < or > depending on whether you are on the right or left. One of those should include =. Your code, as it is, will run through the actual pivot value and do some funky business with it. You end up moving the pivot around in various ways.

  3. You have a potential overrun (buffer overflow) in your loop conditions. It is posible, when you get to this line in the pivoting:

        while(arr[i]<pivot)        i++;

    for i to run off the end of the array. If all the remaining values are less than the pivot, there's nothing stopping it from going off. You still need to check against j in these loops.

Swap

C++ has a swap function, use it. To get it, from C++11 #include<utility> and before that #include<algorithm>

Algorithm

The classic quick-sort is done in 5 stages:

  1. find a 'pivot value'.
  2. move all values less than (or equal to) the pivot value to 'the left'.
  3. move all values larger than the pivot to 'the right'.
  4. quick-sort the values less than(or equal)
  5. quick-sort the values larger than.

Note that many text books extract the first 3 stages in to a 'partitioning' function. The purpose of that function is to identify the pivot value, move the candidates around, and then insert the pivot value back in to the data at 'the right place'.

That last part is key, it leaves the pivot value in the exact place it is supposed to be. This means you never have to include that pivot value in the sorts again.

Let's break that logic down in to it's methods, then, with the assumption that' there's a 'pivoting' function that does the first 3 steps. That leavs a simpler quicksort that looks like:

void quicksort(int *arr, const int left, const int right){    if (left >= right) {        return;    }    int part = partition(arr, left, right);    quicksort(arr, left, part - 1, sz);    quicksort(arr, part + 1, right, sz);}

Notice, in the above, that the check to make sure the inputs are valid are done on entry to the recursive function. This simplifies the last part of the function. The same code could, alternatively, be written similar to yours, as:

void quicksort(int *arr, const int left, const int right, const int sz){    int part = partition(arr, left, right);    std::cout << "QSC:" << left << "," << right << " part=" << part << "\n";    print (arr, sz);    if (left < part - 1) {        quicksort(arr, left, part - 1, sz);    }    if (part + 1 < right) {        quicksort(arr, part + 1, right, sz);    }}

I prefer the first.... it makes it more easy to spot the recursion terminator.

So, that's now a simpler quicksort, partition the data, sort each partition (but not the actual partitioning value which is in the right place).

How do you partition the data?

The trick here is to swap the pivot value to the front of the sequence, partition the rest of the values, and then swap the pivot value back to where it belongs:

int partition(int *arr, const int left, const int right) {    const int mid = left + (right - left) / 2;    const int pivot = arr[mid];    // move the mid point value to the front.    std::swap(arr[mid],arr[left]);    int i = left + 1;    int j = right;    while (i <= j) {        while(i <= j && arr[i] <= pivot) {            i++;        }        while(i <= j && arr[j] > pivot) {            j--;        }        if (i < j) {            std::swap(arr[i], arr[j]);        }    }    std::swap(arr[i - 1],arr[left]);    return i - 1;}

Note how the code above double-checks the buffer overflow?

Putting this all together, and leaving in some of the debug statements you have, I would have the code:

#include <iostream>#include <algorithm>void print(int *a, int n){    int i = 0;    while(i < n){        std::cout << a[i] << ",";        i++;    }    std::cout << "\n";}int partition(int *arr, const int left, const int right) {    const int mid = left + (right - left) / 2;    const int pivot = arr[mid];    // move the mid point value to the front.    std::swap(arr[mid],arr[left]);    int i = left + 1;    int j = right;    while (i <= j) {        while(i <= j && arr[i] <= pivot) {            i++;        }        while(i <= j && arr[j] > pivot) {            j--;        }        if (i < j) {            std::swap(arr[i], arr[j]);        }    }    std::swap(arr[i - 1],arr[left]);    return i - 1;}void quicksort(int *arr, const int left, const int right, const int sz){    if (left >= right) {        return;    }    int part = partition(arr, left, right);    std::cout << "QSC:" << left << "," << right << " part=" << part << "\n";    print (arr, sz);    quicksort(arr, left, part - 1, sz);    quicksort(arr, part + 1, right, sz);}int main() {    int arr[8] = {110, 5, 10,3 ,22, 100, 1, 23};    int sz = sizeof(arr)/sizeof(arr[0]);    print(arr, sz);    quicksort(arr, 0, sz - 1, sz);    print(arr, sz);    return 0;}

I have put this in ideone too.

In general, the code you have is neat, and easy to follow. The QuickSort algorithm is relatively traditional, and the pieces I expect to see are about where I expect them to be.

Let's go through the issues that I see though... and some of them are serious...

Namespaces

Using namespace::std is generally a poor idea. The possibility of namespace pollution is real. In fact, you have implemented a swap function and there already is one std::swap, but we'll get to that.

Style

  • You have a variable min, but this should be mid.

  • sometimes you put the function parameters with the left/right indices before the array, and sometimes after. You have:

    void quicksort(int *arr, int left, int right) {

    and you also have:

    void swap(int i,int j, int *a) {

    pick one, and stick with it. I would personally recommend putting the array first, and the indices afterwards.

  • Use whitespace appropriately. Operators like the << operator on cout are operators like any others. Use the space to improve readability:

    cout<<"QS:"<<left<<","<<right<<"\n";

    should be:

    std::cout << "QS:" << left << "," << right << "\n";

Bugs

You have a few bugs in here which should be addressed:

  1. There's the potential for integer overflow when calculating the mid point. This is a 'famous' bug. Your code int min = (left+right)/2; should be done as:

    int mid = left + (right - left) / 2;

    The above solution will not overflow.

  2. You should, when partitioning the data, consider values equal to the pivot value, to be either left or right of the pivot. Your code you use a strict < or > depending on whether you are on the right or left. One of those should include =. Your code, as it is, will run through the actual pivot value and do some funky business with it. You end up moving the pivot around in various ways.

  3. You have a potential overrun (buffer overflow) in your loop conditions. It is posible, when you get to this line in the pivoting:

        while(arr[i]<pivot)        i++;

    for i to run off the end of the array. If all the remaining values are less than the pivot, there's nothing stopping it from going off. You still need to check against j in these loops.

Swap

C++ has a swap function, use it. To get it, from C++11 #include<utility> and before that #include<algorithm>

Algorithm

The classic quick-sort is done in 5 stages:

  1. find a 'pivot value'.
  2. move all values less than (or equal to) the pivot value to 'the left'.
  3. move all values larger than the pivot to 'the right'.
  4. quick-sort the values less than(or equal)
  5. quick-sort the values larger than.

Note that many text books extract the first 3 stages in to a 'partitioning' function. The purpose of that function is to identify the pivot value, move the candidates around, and then insert the pivot value back in to the data at 'the right place'.

That last part is key, it leaves the pivot value in the exact place it is supposed to be. This means you never have to include that pivot value in the sorts again.

Let's break that logic down in to it's methods, then, with the assumption that' there's a 'pivoting' function that does the first 3 steps. That leavs a simpler quicksort that looks like:

void quicksort(int *arr, const int left, const int right){    if (left >= right) {        return;    }    int part = partition(arr, left, right);    quicksort(arr, left, part - 1, sz);    quicksort(arr, part + 1, right, sz);}

Notice, in the above, that the check to make sure the inputs are valid are done on entry to the recursive function. This simplifies the last part of the function. The same code could, alternatively, be written similar to yours, as:

void quicksort(int *arr, const int left, const int right, const int sz){    int part = partition(arr, left, right);    std::cout << "QSC:" << left << "," << right << " part=" << part << "\n";    print (arr, sz);    if (left < part - 1) {        quicksort(arr, left, part - 1, sz);    }    if (part + 1 < right) {        quicksort(arr, part + 1, right, sz);    }}

I prefer the first.... it makes it more easy to spot the recursion terminator.

So, that's now a simpler quicksort, partition the data, sort each partition (but not the actual partitioning value which is in the right place).

How do you partition the data?

The trick here is to swap the pivot value to the front of the sequence, partition the rest of the values, and then swap the pivot value back to where it belongs:

int partition(int *arr, const int left, const int right) {    const int mid = left + (right - left) / 2;    const int pivot = arr[mid];    // move the mid point value to the front.    std::swap(arr[mid],arr[left]);    int i = left + 1;    int j = right;    while (i <= j) {        while(i <= j && arr[i] <= pivot) {            i++;        }        while(i <= j && arr[j] > pivot) {            j--;        }        if (i < j) {            std::swap(arr[i], arr[j]);        }    }    std::swap(arr[i - 1],arr[left]);    return i - 1;}

Note how the code above double-checks the buffer overflow?

Putting this all together, and leaving in some of the debug statements you have, I would have the code:

#include <iostream>#include <algorithm>void print(int *a, int n){    int i = 0;    while(i < n){        std::cout << a[i] << ",";        i++;    }    std::cout << "\n";}int partition(int *arr, const int left, const int right) {    const int mid = left + (right - left) / 2;    const int pivot = arr[mid];    // move the mid point value to the front.    std::swap(arr[mid],arr[left]);    int i = left + 1;    int j = right;    while (i <= j) {        while(i <= j && arr[i] <= pivot) {            i++;        }        while(i <= j && arr[j] > pivot) {            j--;        }        if (i < j) {            std::swap(arr[i], arr[j]);        }    }    std::swap(arr[i - 1],arr[left]);    return i - 1;}void quicksort(int *arr, const int left, const int right, const int sz){    if (left >= right) {        return;    }    int part = partition(arr, left, right);    std::cout << "QSC:" << left << "," << right << " part=" << part << "\n";    print (arr, sz);    quicksort(arr, left, part - 1, sz);    quicksort(arr, part + 1, right, sz);}int main() {    int arr[8] = {110, 5, 10,3 ,22, 100, 1, 23};    int sz = sizeof(arr)/sizeof(arr[0]);    print(arr, sz);    quicksort(arr, 0, sz - 1, sz);    print(arr, sz);    return 0;}

I have put this in ideone too.

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 屁股又宽又扁怎么办 烫伤一年多色差没有恢复怎么办 上古卷轴5不能动怎么办 上古卷轴ol卡死怎么办 小妹妹这么骚长大了怎么办 同事借钱忘了还怎么办 怀了二胎想离婚怎么办 如果闺蜜疏远了怎么办 考上博士但是硕士要延期怎么办 硕士延期也不能毕业怎么办 硕士论文工作量太少被延期了怎么办 竞彩足球比赛延期中断怎么办? 竞彩足球输了怎么办 讯飞语音不兼容百度怎么办 虎牙直播不兼容语音怎么办 为什么手机打开游戏就死机怎么办 2007cad打开时时死机怎么办 手机qq总是无响应怎么办 英雄联盟登录服务器未响应怎么办 苹果7p照相死机怎么办 小米手机qq打不开怎么办啊 电脑qq老是闪退怎么办 微信摄像卡住了怎么办 电脑打开应用程序没反应怎么办 宇飞来手机锁了怎么办 宇飞来手机忘记密码怎么办 vivo手机突然开不了机怎么办 苹果六关不了机怎么办 微信老是无响应怎么办 打游戏被骂了怎么办 微信运动跳转排行榜失败怎么办 小恩爱账号忘了怎么办 华为账号更换后游戏账号怎么办 注册游戏账号需要身份证怎么办 英雄联盟被裁决了怎么办 百度网盘密码重置不了怎么办 sap密码输入被锁怎么办 dnf二级密码错10怎么办 大网卡网速慢了怎么办 小米路由器无线速度慢怎么办 小米手机无线速度慢怎么办