【数据结构】选择、插入、归并、快排、堆排序总结

a 夏天 提交于 2020-01-20 04:32:01

选择排序:

下面这个选择排序的思路:找出所有元素中最小的元素,然后依次按从小到大放进数组中

# include <iostream>
# include <algorithm>
using namespace std;

void selectionSort(int arr[], int n){


for(int i = 0 ; i < n ; i ++){
    // 寻找[i, n)区间里的最小值
    int minIndex = i;
    for( int j = i + 1 ; j < n ; j ++ )
        if( arr[j] < arr[minIndex] )
            minIndex = j;    //找到最小的元素的位置

    swap( arr[i] , arr[minIndex] );   //和当前的数组的位置进行交换


}

int main() {

int a[10] = {10,9,8,7,6,5,4,3,2,1};
selectionSort(a,10);
for( int i = 0 ; i < 10 ; i ++ )
    cout<<a[i]<<" ";
cout<<endl;

return 0;

}

//此处用到了模板类,可以方便测试的时候进行任何数值去测试
template<typename T>
void selectionSort(T arr[], int n){

for(int i = 0 ; i < n ; i ++){

    int minIndex = i;
    for( int j = i + 1 ; j < n ; j ++ )
        if( arr[j] < arr[minIndex] )
            minIndex = j; 

    swap( arr[i] , arr[minIndex] );  
}

}

插入排序:

此次插入排序的思路:从数组中依次找一个元素,然后把这个元素与之前已经排好序的n个元素作比较,如果前面的n个元素大于此元素,则*交换位置*,此算法未进行优化!!!

template<typename T>
void insertionSort(T arr[], int n){

for( int i = 1 ; i < n ; i ++ ) {

    // 寻找元素arr[i]合适的插入位置
    // 写法1


//        for( int j = i ; j > 0 ; j-- )
//            if( arr[j] < arr[j-1] )
//                swap( arr[j] , arr[j-1] );    
//            else
//                break;


  // 写法2 
    for( int j = i ; j > 0 && arr[j] < arr[j-1] ; j -- )
        swap( arr[j] , arr[j-1] );

}

return;
}

已优化的算法:即把交换改成比较,比较完前n个数,确定是在此位置才进行交换

 // 写法3
        T e = arr[i];
        int j; // j保存元素e应该插入的位置
        for (j = i; j > 0 && arr[j-1] > e; j--)
            arr[j] = arr[j-1]; //关键是这一步,满足条件就往后移
        arr[j] = e;//找到位置,交换

对于上面两种O(n^2)级别的排序方法,但是插入排序相对于选择排序是更好的,因为插入排序对于近乎有序的数组,时间复杂度可以达到O(n),这是比后面O(nlog2n)的算法的时间复杂度还要低的,所以在一般的情况下,在处理近乎有序的数值的时候,我们要选用插入排序!

归并排序:

自顶向下的归并排序,将数组分成一半,一层一层地分,每一层都是除以2来分,分完之后,从底向上对每一部分进行递归排序,形成了每一部分都是有序的数组,然后到最后一层时,我们要进行归并,这时候,我们需要把所有的数组复制一份(这里就占用了O(n)的空间复杂度),然后需要三个位置的索引,第一第二个索引分别指向两个有序的数组第三个所以指向最终的数组,最后通过第一第二个索引的数值进行比较找到最小的那个元素,进而把元素赋值给第三个索引的值。

// 将arr[l...mid]和arr[mid+1...r]两部分进行归并  
template<typename  T>
void __merge(T arr[], int l, int mid, int r){

// 经测试,传递aux数组的性能效果并不好
T aux[r-l+1];
for( int i = l ; i <= r; i ++ )
    aux[i-l] = arr[i];      //复制

int i = l, j = mid+1;
for( int k = l ; k <= r; k ++ ){

    if( i > mid )   { arr[k] = aux[j-l]; j ++;}
    else if( j > r ){ arr[k] = aux[i-l]; i ++;}
    //上面两个条件判断,是先判断有无越界,如果比较完之后,任何一边都又剩余的元素,则要把剩余的元素复制到数组的后面
    
    //在arr[l...mid]和arr[mid+1...r]两部分找最小值
    else if( aux[i-l] < aux[j-l] ){ arr[k] = aux[i-l]; i ++;}
    else                          { arr[k] = aux[j-l]; j ++;}
}


}

// 递归使用归并排序,对arr[l...r]的范围进行排序
template<typename T>
void __mergeSort(T arr[], int l, int r){

if( l >= r )    //递归出口
    return;

int mid = (l+r)/2;
    
 //对划分的每一部分进行排序
__mergeSort(arr, l, mid);
__mergeSort(arr, mid+1, r);
//对最后一部分进行排序
__merge(arr, l, mid, r);


}

template<typename T>
void mergeSort(T arr[], int n){

__mergeSort( arr , 0 , n-1 );

}

自底向上的归并排序:第一个for循环时对数组进行划分,第二层循环是对划分的两部分数组进行排序。

template <typename T>
void mergeSortBU(T arr[], int n){

//    for( int sz = 1; sz <= n ; sz += sz )
//        for( int i = 0 ; i+sz < n ; i += sz+sz )
//            // 对 arr[i...i+sz-1] 和 arr[i+sz...i+2*sz-1] 进行归并
//            __merge(arr, i, i+sz-1, min(i+sz+sz-1,n-1) );

    // Merge Sort Bottom Up 优化
    for( int i = 0 ; i < n ; i += 16 )
        insertionSort(arr,i,min(i+15,n-1));

    for( int sz = 16; sz <= n ; sz += sz )
        for( int i = 0 ; i < n - sz ; i += sz+sz )
            if( arr[i+sz-1] > arr[i+sz] )
                __merge(arr, i, i+sz-1, min(i+sz+sz-1,n-1) );
}

快排:

先取一个基准,这个取基准的方法就是partition方法,并且返回基准的索引,这个索引就是基准的最终位置

// 对arr[l...r]部分进行partition操作
// 返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int __partition(T arr[], int l, int r){    //l是基准

    T v = arr[l];

    int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v        //j是分界点
    for( int i = l + 1 ; i <= r ; i ++ )     //i是当前访问的元素
        if( arr[i] < v ){   //主要是讨论小于的部分,大于的部分不用变
            j ++;
            swap( arr[j] , arr[i] );     //放到j的后面
        }

    swap( arr[l] , arr[j]);    //最后一步,基准要放到j的位置

    return j;
}

// 对arr[l...r]部分进行快速排序
template <typename T>
void __quickSort(T arr[], int l, int r){

    if( l >= r )
        return;

    int p = __partition(arr, l, r);  
    //递归实现两部分的快排
    __quickSort(arr, l, p-1 );
    __quickSort(arr, p+1, r);
}

template <typename T>
void quickSort(T arr[], int n){

    __quickSort(arr, 0, n-1);
}

堆排序

二叉堆是一棵完全二叉树,它也是一个树形结构,根节点是声明成1,i/2是根节点,2i是左孩子,2i+1是右孩子

shift up:


template<typename Item>
class MaxHeap{

private:
    Item *data;
    int count;
    int capacity;

    void shiftUp(int k){
        while( k > 1 && data[k/2] < data[k] ){//k>1是考虑越界问题
            swap( data[k/2], data[k] );
            k /= 2;  //更新k,依然找下一个父节点
        }
    }

public:

    MaxHeap(int capacity){
        data = new Item[capacity+1];
        count = 0;
        this->capacity = capacity;
    }

    ~MaxHeap(){
        delete[] data;
    }

    int size(){
        return count;
    }

    bool isEmpty(){
        return count == 0;
    }

	//在堆中插入一个元素,
    void insert(Item item){
        assert( count + 1 <= capacity );
        data[count+1] = item;
        count ++;
        shiftUp(count);
    }


public:
    void testPrint(){

        if( size() >= 100 ){
            cout<<"Fancy print can only work for less than 100 int";
            return;
        }

        if( typeid(Item) != typeid(int) ){
            cout <<"Fancy print can only work for int item";
            return;
        }

        cout<<"The Heap size is: "<<size()<<endl;
        cout<<"data in heap: ";
        for( int i = 1 ; i <= size() ; i ++ )
            cout<<data[i]<<" ";
        cout<<endl;
        cout<<endl;

        int n = size();
        int max_level = 0;
        int number_per_level = 1;
        while( n > 0 ) {
            max_level += 1;
            n -= number_per_level;
            number_per_level *= 2;
        }

        int max_level_number = int(pow(2, max_level-1));
        int cur_tree_max_level_number = max_level_number;
        int index = 1;
        for( int level = 0 ; level < max_level ; level ++ ){
            string line1 = string(max_level_number*3-1, ' ');

            int cur_level_number = min(count-int(pow(2,level))+1,int(pow(2,level)));
            bool isLeft = true;
            for( int index_cur_level = 0 ; index_cur_level < cur_level_number ; index ++ , index_cur_level ++ ){
                putNumberInLine( data[index] , line1 , index_cur_level , cur_tree_max_level_number*3-1 , isLeft );
                isLeft = !isLeft;
            }
            cout<<line1<<endl;

            if( level == max_level - 1 )
                break;

            string line2 = string(max_level_number*3-1, ' ');
            for( int index_cur_level = 0 ; index_cur_level < cur_level_number ; index_cur_level ++ )
                putBranchInLine( line2 , index_cur_level , cur_tree_max_level_number*3-1 );
            cout<<line2<<endl;

            cur_tree_max_level_number /= 2;
        }
    }

private:
    void putNumberInLine( int num, string &line, int index_cur_level, int cur_tree_width, bool isLeft){

        int sub_tree_width = (cur_tree_width - 1) / 2;
        int offset = index_cur_level * (cur_tree_width+1) + sub_tree_width;
        assert(offset + 1 < line.size());
        if( num >= 10 ) {
            line[offset + 0] = '0' + num / 10;
            line[offset + 1] = '0' + num % 10;
        }
        else{
            if( isLeft)
                line[offset + 0] = '0' + num;
            else
                line[offset + 1] = '0' + num;
        }
    }

    void putBranchInLine( string &line, int index_cur_level, int cur_tree_width){

        int sub_tree_width = (cur_tree_width - 1) / 2;
        int sub_sub_tree_width = (sub_tree_width - 1) / 2;
        int offset_left = index_cur_level * (cur_tree_width+1) + sub_sub_tree_width;
        assert( offset_left + 1 < line.size() );
        int offset_right = index_cur_level * (cur_tree_width+1) + sub_tree_width + 1 + sub_sub_tree_width;
        assert( offset_right < line.size() );

        line[offset_left + 1] = '/';
        line[offset_right + 0] = '\\';
    }
};

shif down:在取出元素的时候,要保持完全二叉树的形态

  void shiftDown(int k){
        while( 2*k <= count ){   //count是堆的数目
            int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置
            if( j+1 <= count && data[j+1] > data[j] )
                j ++;
            // data[j] 是 data[2*k]和data[2*k+1]中的最大值

            if( data[k] >= data[j] ) break;
            swap( data[k] , data[j] );   //这一步其实还是可以优化的,可以把swap改成是赋值操作,通过开辟新的空间来给满足条件的值一个一个赋值
            k = j;   //向下移动k的位置
        }
    }
    Item extractMax(){
        assert( count > 0 );
        Item ret = data[1]; //取出根节点

        swap( data[1] , data[count] );
        count --;
        shiftDown(1);

        return ret;
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!