数据结构(一):查找和排序

1.插入排序

插入排序 复杂度t=O(n的平方)
将一个记录插入到已排好序的有序表中,从而到一个新的长度++的有序表
具体操作:

  1. 数i与数i-1比,如果小,则把数i放在data[0]中作哨兵
  2. 哨兵依次与数i-2及之前的数(以数j表示之)相比,如果哨兵小,则数j后移,否则覆盖在数j后面的位置上
  3. 注意,当–j一直到0的时候,data[0]
void insert_sort(int data[], int n) {//data[0] 没有使用
    int i, j;
    for (i = 2; i <= n; ++i) { // i从1开始
        if (data[i] < data[i-1]) {
            data[0] = data[i];  // 设置哨兵
            for (j = i-1; data[0] < data[j]; --j) {  
                data[j+1] = data[j];  // 前面的覆盖后面的
            }
            data[j+1] = data[0]; // 永远都是被别人覆盖的放在左边
        }
    }
}//insert_sort

2. 冒泡排序

复杂度t=O(n的平方)
交换序列中相邻两个整数为基本操作
将被排序的记录数组data[0..n-1]垂直排列,每个记录data[i]看作气泡。
根据轻气泡不能在重气泡之下的原则,从上往下扫描数组data, 较重者下沉。
如此反复进行,重气泡依次下沉直到最后有序

void bubble_sort(int data[], int n) {  
    int i, j;  
    bool tag = true;  

    for (i = n-1; i > 0 && tag; --i) { // 代表需要排序的序列长度依次减少  
        tag = false;  
        for (j = 0; j < i; ++j) {  
            if (data[j] > data[j+1]) {  
                swap(data[j], data[j+1]);  
                tag = true;  
            }  
        }  
    }  
}//bubble_sort 

3.二分查找

对有序数组进行二分查找

/* 
 有序数组二分查找,非递归 
 return: 若找到则到返回相应的位置下标,否则返回-1 
*/  
int bsearch(int data[], int n, int key) {  
    int left = 0, right = n -1;  
    while (left <= right) {  
        int mid = (left + right) / 2;  
        if (data[mid] == key) {  
            return mid;  
        }  
        if (data[mid] > key) {  
            right = mid - 1;  
        } else {  
            left = mid + 1;  
        }  
    }  
    return -1;  
}  

//递归实现  
//data有序数组  
int bsearch(int data[], int left, int right, int key) {  
    if (left > right) {  
        return -1;  
    }  

    int mid = (left + right) / 2;  
    if (data[mid] == key) {  
        return mid;  
    }  
    if (data[mid] < key) {  
        return bsearch(data, mid, right, key);  
    }  
    return bsearch(data, left, mid, key);  
}  

4. 快速排序

复杂度最坏O(n的平方),平均O(nlogn)**辅助存储O(logn)。
是对冒泡的一种改进,也是交换排序
思想:通过一趟排序将数组列分成两个独立的部分,其中一部分的值都比另一部分的小,则分别对这两部分继续进行快速排序,直到整个序列有序

具体操作:快排由两个函数构成,一个是partition根据枢轴将数据data[]分区,另一个是quick_sort,它先调用partition进行一次划分,然后对前后两个分区各自递归调用quick_sort进行快排。

/* 
返回划分两区后,枢轴的位置 
data[low...high]中,返回枢轴的位置,在它之前的元素值都不大于它,在它之后的都不小于它 
*/  
int partition(int data[], int low, int high) {  
    int pivot = data[low]; // 枢轴,总是第一个数,根据它来区分两组的。  

    while (low < high) {  
        while (low < high && data[high] >= pivot) { // 从high位置开始往前找,直到找到比pivot小的数  
            --high;   
        } //此时data[high]<pivot  
        data[low] = data[high]; //将data[high]存于data[low]中,此时data[high]为空位  
        while (low < high && data[low] <= pivot) { // low位置值与pivot比,低位下标++  
            ++low;  
        } //此时data[low]>pivot  
        data[high] = data[low]; // data[low]存于data[high]中。此时data[low]为空位  
    }  
    //while结束时,data[low]一定是空位  

    data[low] = pivot;  
    return low; //返回的low值一定就是pivot在新数组中的下标  
}  
void quick_sort(int data[], int low, int high) {  
    if (low < high) {  
        int k = partition(data, low, high);  
        quick_sort(data, low, k-1);  
        quick_sort(data, k+1, high);  
    }  
}// quick_sort  

5. 希尔排序

先将整个待排记录分成若干个子序列,再进行直接插入排序
待全体基本有序时,再对全体进行一次直接插入排序
(子序列元素不一定相邻,而是相隔某个增量的记录组成的一个子序列)

void shell_insert(int data[], const int n, const int d) { // d是增量  data[0]没使用  
    int i, j, tmp;  

    // 对每个子序列进行插入排序  
    for (i = d; i <= n; i++) {   
        data[0] = data[i];  
        for (j = i-d; (j > 0) && (data[0] < data[j]); j-=d) { // 这里和普通的直接插入排序的区别就是必须要判断j>0,因为j每次递减的是d大小,哨兵可能拦截不住  
            data[j+d] = data[j];  
        }  
        data[j+d] = data[0];  
    }  
}  
/* 
params:  
    data        : 待排数组,数据存在data[1...n]中,data[0]为空位,留给哨兵 
    darr        : 递增值d构成的数组。按照最后进行一次插入排序的要求,最后一个值是1 
    len_data    : data的长度 
    len_darr    : darr的长度 
*/  
void shell_sort(int data[], int darr[], const int len_data, const int len_darr) {  
    for (int i = 0; i < len_darr; ++i) {  
        shell_insert(data, len_data, darr[i]);  
    }  
}//shell_sort  

6. 简单选择排序

基本思想:每一趟在n-(i-1)个记录中选取值最小的记录作为有序序列的第i个元素。

void simple_select_sort(int data[], int n) {  
    int i, j, min_index;  

    for (i = 0; i < n; ++i) {  
        min_index = i; // min_index是当前最小元素的下标。存下标而不存变量值,免去频繁交换数据的效率问题  
        for (j = i+1; j < n; ++j) {   // 注意i+1  
            if (data[j] < data[min_index]) {  
                min_index = j;  
            }  
        }  
        if (i != min_index) {  
            swap_int(data[i], data[min_index]);  
        }  
    }  
} // simple_select_sort  

7. 堆排序

最坏=平均=O(nlogn),不需要辅助存储,没有使用data[0]作暂存
将一个无序序列放到一个完全二叉树中。
调整为一个大顶堆(输出的时候就从大到小输出,序列从尾部向前伸展变成有序列)
输出堆顶后,重新调整为大顶堆

将大量的时间花在建堆上,适用于n较大的情况

/* 
heap_adjust是将data调整为大顶堆。 
已知data[start...end]除元素data[start]外均满足大顶堆的定义(父亲结点总是比孩子结点值大) 
调整为大顶堆,就是让data[start]下沉到适当的位置上去。 
初始假设data[n/2+1...n-1]已经满足大顶堆的定义(因为它们是叶子节点,没有孩子)。 
初次调整时start为最后一个非叶子结点,即n/2(n是data的长度),start依次减小,直至为0。 
*/  
void heap_adjust(int data[], int start, int end) {  
    //一棵子树中,父结点与大孩子交换值(交换之后,为了保证大孩子这棵子树满足堆的定义,沿大孩子方向向下)  
    for (int child_index = start * 2; child_index <= end; child_index *= 2) {  
        if (child_index < end && data[child_index] < data[child_index+1]) {  
            ++ child_index;   
        }// i现在是data[start]的较大孩子的下标  
        if (data[start] >= data[child_index]) {// data[start]与它的较大值的孩子比较;只有孩子比它大时,才作交换  
            break;   
        }  
        swap_int(data[start], data[child_index]); // 父结点与大孩子交换值  
        start = child_index; // 沿着较大孩子向下  
    }  
}  
/* 
有序序列从后向前延伸. 
*/  
void heap_sort(int data[], int n) {  
    //初始调整为大顶堆,调整后data[0]为最大值  
    for (int i = n/2; i >= 0; --i) { // i从最后一个非叶子结点开始,倒序遍历  
        heap_adjust(data, i, n-1);  
    } //当前data是大顶堆  

    for (int i = n-1; i > 0; --i) {  
        swap_int(data[0], data[i]); //交换之后,data[i]及之后的元素是有序序列, data[0...i-1]除data[0]之外已满足大顶堆的定义  
        heap_adjust(data, 0, i-1); //让data[0]下沉到相应的位子上(持续向下跟大孩子作交换)。调整之后data[0...i-1]是个大顶堆。  
    }  
}// heap_sort  

8. 归并排序

最坏=平均=O(nlogn),辅助存储O(n)
初始序列可看成是n个有序的子序列,子序列长度为1,然后两两归并,得到长度为2的有序子序列。再两两归并。。。。
直到得到一个长度为n的一个有序列为止
这叫2路归并排序(使用递归实现,从大处着手,但是从递归到两两相邻时的深度时才开始交换)

要进行logn趟归并

void merge(int data[], int left, int mid, int right) {  
    //new一个辅助存储空间tmp数组, 将有序的tmp[left..mid]和tmp[mid+1..right]归并为有序的data[left...right]  

    int *tmp = new int[right - left + 1];  
    int i = left;   
    int j = mid + 1;   
    int k = 0;  
    while (i <= mid && j <= right) {  
        if (data[i] <= data[j]) {  
            tmp[k++] = data[i++];  
        } else {  
            tmp[k++] = data[j++];  
        }  
    }  
    while (i <= mid) {  
        tmp[k++] = data[i++];  
    }  
    while (j <= right) {  
        tmp[k++] = data[j++];  
    }  

    // 复制回来  
    i = left;  
    k = 0;  
    while (i <= right) {  
        data[i++] = tmp[k++];  
    }  
    delete[] tmp;  
}  

void merge_sort(int data[], int left, int right) { // 最后结果放在在data中  
    if (left >= right) {  
        return;  
    }  
    int mid = (left + right) / 2;  
    merge_sort(data, left, mid);//对左半区进行归并排序  
    merge_sort(data, mid+1, right);//对右半区进行归并排序  
    merge(data, left, mid, right);//现在,左右两个半区都是有序的了,现在把两个有序的序列归并起来。  
} //merge_sort  
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值