算法 - 排序

169 阅读7分钟

冒泡排序

从头开始比较每一对相邻的元素,如果第1个比第2个元素大,就交换它们的位置。执行完一轮之后,最末尾的那个元素就是最大的元素。

int main() {
    using namespace std;
    static const int MAX = 6;
    int m[MAX] = {5,6,4,3,2,1};
    // 外层确定交换的结束位置
    for (int end = MAX; end > 0; end--) {
        // 里层确定交换
        for (int i = 1; i < end; ++i) {
            if (m[i] < m[i - 1]) {
                int temp = m[i];
                m[i] = m[i-1];
                m[i-1] = temp;
            }
        }
    }

    for (int j = 0; j < MAX; ++j) {
        cout << m[j] << "_";
    }
    return 0;
}

优化一: 如果已经有序,提前结束排序

#include <iostream>

void swap(int &a, int &b);

int main() {
    using namespace std;
    static const int MAX = 6;
    int m[MAX] = {5,6,4,3,2,1};
    // 外层确定交换的结束位置
    for (int end = MAX; end > 0; end--) {
        // 默认有序
        bool isSorted = true;
        // 里层确定交换
        for (int i = 1; i < end; ++i) {
            if (m[i] < m[i - 1]) {
                swap(m[i-1],m[i]);
                // 如果进行了交换,证明序列无序
                isSorted = false;
            }
        }
        // 如果已经有序,结束排序
        if (isSorted) break;
    }

    for (int j = 0; j < MAX; ++j) {
        cout << m[j] << "_";
    }
    return 0;
}

void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

优化二: 如果序列尾部已经局部有序,可以记录最后1次交换的位置,减少比较次数

#include <iostream>

void swap(int &a, int &b);

int main() {
    using namespace std;
    static const int MAX = 6;
    int m[MAX] = {5,6,4,3,2,1};
    // 外层确定交换的结束位置
    for (int end = MAX; end > 0; end--) {
        // 如果初始完全有序的情况,要直接结束掉遍历,因此要确保只进行一次外层遍历,
        // 即end--之后,小于等于0了
        int sortEnd = 0;
        // 里层确定交换
        for (int i = 1; i < end; ++i) {
            if (m[i] < m[i - 1]) {
                swap(m[i-1],m[i]);
                // 确定部分有序的位置
                sortEnd = i+1;
            }
        }
        end = sortEnd;
    }

    for (int j = 0; j < MAX; ++j) {
        cout << m[j] << "_";
    }
    return 0;
}

void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

选择排序

从序列中找出最大的元素,然后与最末尾的元素交换位置

选择排序的交换次数要远远少于冒泡排序 ,平均性能优于冒泡排序

#include <iostream>

void swap(int &a, int &b);

int main() {
    using namespace std;
    static const int MAX = 6;
    int m[MAX] = {5,6,4,3,2,1};
    for (int end = MAX; end > 0; end--) {
        int maxIndex = 0;
        for (int i = 1; i < end; ++i) {
            if(m[i] > m[maxIndex]) {
                maxIndex = i;
            }
        }
        swap(m[maxIndex],m[end-1]);
    }

    for (int j = 0; j < MAX; ++j) {
        cout << m[j] << "_";
    }
    return 0;
}

void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

插入排序

  • 在执行过程中,插入排序会将序列分为2部分
    • 头部是已经排好序的,尾部是待排序的
  • 从头开始扫描每一个元素
    • 每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数据依然保持有序
#include <iostream>

void swap(int &a, int &b);

int main() {
    using namespace std;
    static const int MAX = 6;
    int m[MAX] = {5,6,4,3,2,1};
    for (int begin = 1; begin < MAX; ++begin) {
        int cur = begin;
        while (cur > 0 && m[cur] < m[cur-1]) {
            swap(m[cur],m[cur-1]);
            cur--;
        }
    }

    for (int j = 0; j < MAX; ++j) {
        cout << m[j] << "_";
    }
    return 0;
}

void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

优化一:将交换转变为挪动

int main() {
    using namespace std;
    int m[] = {5,6,4,3,2,1,7};
    int count = sizeof(m) / sizeof(int);
    for (int begin = 1; begin < count; ++begin) {
        int cur = begin, temp = m[cur];
        while (cur > 0 && temp < m[cur-1]) {
           m[cur] = m[cur-1];
           cur--;
        }
        m[cur] = temp;
    }

    for (int j = 0; j < count; ++j) {
        cout << m[j] << "_";
    }
    return 0;
}

优化二:二分搜索查找插入位置

二分搜索插入位置,注意数组设置为左闭右开,便于数据使用

#include <iostream>

int searchIndex(int m[], int count, int v);

int main() {
    using namespace std;
    int m[] = {5,6,4,3,2,1,7};
    int count = sizeof(m) / sizeof(int);
    for (int begin = 1; begin < count; ++begin) {
        // 备份当前数值
        int temp = m[begin];
        // 通过二分查找,找到要插入的位置
        int cur = searchIndex(m,begin,temp);
        // 移动其余元素
        for (int i = begin; i > cur ; i--) {
            m[i] = m[i-1];
        }
        // 将元素插入到对应的位置
        m[cur] = temp;
    }

    for (int j = 0; j < count; ++j) {
        cout << m[j] << "_";
    }
    return 0;
}

// 数组采用左闭右开策略,即[0,count)
int searchIndex(int m[], int count, int v) {
    int begin = 0;
    int end = count;

    // 当begin == end 时候,就是查找到了对应的位置
    while (begin < end) {
        int mid = (begin + end) >> 1;
        if (v < m[mid]) {
            end = mid;
        } else {
            begin = mid + 1;
        }
    }
    return begin;
}

归并排序

  • 1945年由冯诺依曼首次提出。
  • 执行流程
    • 不断的将当前序列平均分割成2个子序列,直到不能再分割(序列中只剩1个元素)
    • 不断地将2个子序列合并成1个有序序列,知道最终只剩下1个有序序列
#include <iostream>
void mergeSort(int array[],int leftArray[],int begin, int end);
void merge(int array[],int leftArray[], int begin, int end, int mid);

int main() {
    using namespace std;
    int m[] = {5,6,4,3,2,1,7};
    int count = sizeof(m) / sizeof(int);
    int leftArray[count >> 1];
    mergeSort(m,leftArray,0,count);
    for (int j = 0; j < count; ++j) {
        cout << m[j] << "_";
    }
    return 0;
}

void mergeSort(int array[],int leftArray[],int begin, int end) {
    // 如果元素数量只有一个,结束递归
     if (end - begin < 2) return;
     int mid = (begin + end) >> 1;
      // 利用递归,进行拆分操作,递归的结束标志是只剩下唯一的元素
     mergeSort(array,leftArray,begin,mid);
     mergeSort(array,leftArray,mid,end);
     // 进行元素的合并操作
     merge(array,leftArray,begin,end,mid);
}

void merge(int array[],int leftArray[], int begin, int end, int mid) {
// li是左半部分的起点,le是左半部分的终点,因为左闭右开的性质,mid-begin也就是左半部分的元素个数,同时也是左闭右开情况下,左半部分的终点
    int li = 0, le = mid - begin;
    int ri = mid, re = end;
    int ai = begin;
    // 1. 备份左半部分数据到新的数组
    for (int i = li; i < le; ++i) {
        leftArray[i] = array[i+begin];
    }

    // 2. 判断左半部分是否完全完成,如果左半部分完成归并,意味着整个归并结束
    while (li < le) {
        // 如果左边元素较大,将右半部分元素放入数组
        if (ri < re && array[ri] < leftArray[li] ) {
            array[ai++] = array[ri++];
        } else {
         // 如果右办部分结束了,直接将左半部分的元素导入数组
            array[ai++] = leftArray[li++];
        }
    }
}

快速排序

1960年由爵士东尼·霍尔提出

执行流程

  • 从序列中选择一个轴点元素(pivot)
    • 假设每次选择0位置的元素为轴点元素
  • 利用pivot将序列分割成2个子序列
    • 将小于pivot的元素放在pivot前面(左侧)
    • 将大于pivot的元素放在pivot后面(右侧)
    • 等于pivot的元素放哪边都可以
  • 对子序列进行上述两步操作
    • 直到不能再分割(子序列只剩下1个元素)

快速排序的本质:逐渐将每一个元素都转换成轴点元素

#include <iostream>

void sort(int* array,int begin, int end);
int pivotAtIndex(int* array,int begin,int end);
void swap(int &a, int &b);

int main() {
    using namespace std;
    int m[] = {5,6,4,1,3,7};
    int num = sizeof(m) / sizeof(int);
    sort(m,0,num);
    for (int j = 0; j < num; ++j) {
        cout << m[j] << "_";
    }

    return 0;
}

void sort(int* array,int begin, int end) {
    // 如果只有一个元素结束
    if(end-begin < 2) return;
    // 确定轴点元素位置O(n)
    int pivot = pivotAtIndex(array,begin,end);
    sort(array,begin,pivot);
    sort(array,pivot + 1,end);
}
/**
 * 构造出 [begin, end) 范围的轴点元素
 * @return 轴点元素的最终位置
 * */
int pivotAtIndex(int* array,int begin,int end) {
    // 随机选择一个元素跟begin位置进行交换 (降低出现最坏排序的概率)
    swap(array[begin],array[begin + arc4random() % (end - begin)]);
    // 备份轴点(begin)元素的位置
    int pivot = array[begin];
    // 左闭右开空间
    end--;
    while (begin < end) {
        while (begin < end) {
            // 从end开始进行遍历,因为begin的元素已经备份,避免了元素的浪费
            if (pivot < array[end]) {
                end--;
            } else {
                // begin增加后转到从begin进行遍历
                array[begin++] = array[end];
                break;
            }
        }
        while (begin < end) {
            if (pivot > array[begin]) {
                begin ++;
            } else {
                array[end--] = array[begin];
                break;
            }
        }
    }
    array[begin] = pivot;
    return begin;
}

void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

希尔排序

#include <iostream>

void swap(int &a, int &b);
void logArray(int *array,int count);
void sort(int *array,int arrayCount,int step);

int main() {
    using namespace std;
    int m[] = {5,6,4,1,3,7};
    int num = sizeof(m) / sizeof(int);
    int tempNumber = num;
    while (tempNumber >>= 1 > 0) {
        sort(m,num,tempNumber);
    }
    logArray(m,num);
    return 0;
}

void sort(int *array,int arrayCount,int step) {
    for (int col = 0; col < step; ++col) {
        for (int begin = col + step; begin < arrayCount; begin += step) {
            int cur = begin;
            while (cur > col && array[cur] < array[cur - step]) {
                swap(array[cur],array[cur-step]);
                cur -= step;
            }
        }
    }
}


void logArray(int *array,int num) {
    for (int j = 0; j < num; ++j) {
        std::cout << array[j] << "_";
    }
}

void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}