1.查找
线性表、树表、哈希表
1.1线性表
顺序查找、折半查找、分块查找
1.1.1顺序查找。
顺序查找。
1.1.2折半查找
折半查找,只适用于有序表,且仅限于顺序存储结构,对线性链表无法进行有效的折半查找(折半查找low会指向一个元素而high会指向相邻的下一个元素,然后low和high指向同一个元素,再然后low>high,循环结束,所以循环结束的条件为low<=high)。注意不要忘记最会返回-1。
#include"stdafx.h"
#include<iostream>
using namespace std;
//返回所查找数值在数组中的位置,如果没有则返回-1
int binarySearch(int* data, int n, int v){
if(data == NULL || n <= 0)
return -1;
int low = 0;
int high = n - 1;
int middle;
while(low <= high){
middle = (low + high)/2;
if(data[middle] == v)
return middle;
if(data[middle] < v)
low = middle + 1;
else
high = middle -1;
}
return -1;
}
int main(){
int data[] = {1,2};
cout<<binarySearch(data, 2 ,2);
}
1.1.3分块查找(索引顺序查找)
分块查找:分块查找又称为索引顺序查找,索引表采用(折半查找),块内(顺序查找)处理线性表既希望有较快的查找速度又需要动态的变化,则可以采用分块查找的方法。
1.2树表
二叉排序树(二叉查找树)、平衡二叉树
1.3哈希表
哈希表的构造方法:直接定址法、数字分析法、平方取中法、折叠法、除留取余法、随机数法。
解决冲突的方法: 开放定址法、再哈希法、链地址法。
2.排序
插入排序、快速排序、选择排序、归并排序
2.1插入排序
直接插入排序、折半插入排序、希尔排序
2.1.1直接插入排序
待排记录的数量很小时使用。
#include"stdafx.h"
#include<iostream>
using namespace std;
void insertSort(int* a,int n){
if(a == NULL || n <= 0){
return;
}
int i,j,temp;
for(i = 1; i < n; i++){
for(j = i; j > 0 && a[j] < a[j-1]; j--){
temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
}
}
}
int main(){
int a[] = {5,3,6,3,1,4,7,2};
insertSort(a,8);
for(int i = 0; i < 8; i++){
cout<<a[i]<<" ";
}
}
2.1.2折半插入排序
在直接插入排序的基础上减少比较的次数。
算法关键:1.折半查找最后high和low会指向同一个元素,所以循环中判定条件为low<=high
2.关键字相同时,要到高半区,包装算法的稳定性
3.元素插入位置在high+1处
#include"stdafx.h"
#include<iostream>
using namespace std;
void binaryInsertSort(int* a,int n){
if(a == NULL || a <= 0)
return;
int low;
int high;
int middle;
int temp;
for(int i = 1; i < n; i++){
low = 0;
high = i -1;
//折半查找最后low和high会指向同一个元素
while(low <= high){
middle = (low + high)/2;
//关键字相同时,到高半区,保证稳定性
if(a[i] >= a[middle]){
low = middle + 1;
}else{
high = middle -1;
}
}
temp = a[i];
for(int j = i; j >= high + 2; j--){
a[j] = a[j-1];
}
//插入位置在high+1处
a[high + 1] = temp;
}
}
int main(){
int a[] = {5,3,6,3,1,4,7,2};
binaryInsertSort(a,8);
for(int i = 0; i < 8; i++){
cout<<a[i]<<" ";
}
}
2.1.3希尔排序
当待排记录序列基本有序且数目较少时,直接插入排序效率较高,希尔排序正是从这两点分析出发对直接插入排序进行改进得到的一种插入排序算法。
#include"stdafx.h"
#include<iostream>
using namespace std;
void shellInsert(int* a,int n, int k){
if(a == NULL || a <= 0 || k <= 0)
return;
int i,j,temp;
for(i = k; i < n; i++){
for(j = i; j > 0;){
if(a[j] < a[j-k]){
temp = a[j];
a[j] = a[j-k];
a[j-k] = temp;
}
j = j-k;
}
}
}
void shellSort(int* a ,int n,int ks[],int t){
for(int i = 0; i < t; i++){
shellInsert(a,n,ks[i]);
}
}
int main(){
int ks[] = {4,2,1};
int a[] = {5,3,6,3,1,4,7,2};
shellSort(a,8,ks,3);
for(int i = 0; i < 8; i++){
cout<<a[i]<<" ";
}
}
2.2快速排序
冒泡排序和快速排序是借助“交换”进行排序的方法。
2.2.1冒泡排序
一般算法(最好情况下时间复杂度也为n^2):
#include "stdafx.h"
#include <iostream>
using namespace std;
void bubbleSort(int* a, int n){
if(a == NULL || n <=0){
return;
}
int i,j,temp;
for(i = 0; i < n; i++){
for(j = n-1; j > i; j--){
if(a[j] < a[j-1]){
temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
}
}
}
}
int main(){
int a[] = {5,3,6,3,1,4,7,2};
bubbleSort(a,8);
for(int i = 0; i < 8; i++){
cout<<a[i]<<" ";
}
}
优化算法,往上冒(最好情况下时间复杂度为n):
#include "stdafx.h"
#include <iostream>
using namespace std;
void bubbleSort(int* a, int n){
if(a == NULL || n <=0){
return;
}
int i,j,temp;
bool move;
for(i = 0; i < n; i++){
move = false;
for(j = n-1; j > i; j--){
if(a[j] < a[j-1]){
temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
move = true;
}
}
if(!move){
break;
}
}
}
int main(){
int a[] = {5,3,6,3,1,4,7,2};
bubbleSort(a,8);
for(int i = 0; i < 8; i++){
cout<<a[i]<<" ";
}
}
优化算法,往下沉(最好情况下时间复杂度为n):
#include "stdafx.h"
#include <iostream>
using namespace std;
void bubbleSort(int* a, int n){
if(a == NULL || n <= 0){
return;
}
int i,j,temp;
bool move;
for(i = n-1; i >=1; i--){
move = false;
for(j =0; j < i; j++){
if(a[j] > a[j+1]){
temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
move = true;
}
}
if(!move)
break;
}
}
int main(){
int a[] = {5,3,6,3,1,4,7,2};
bubbleSort(a,8);
for(int i = 0; i < 8; i++){
cout<<a[i]<<" ";
}
}
2.2.2快速排序
include"stdafx.h"
#include<iostream>
using namespace std;
void quickSort(int* a,int l,int h){
if(a == NULL || l < 0 || h < 0){
cout<<"invalid param"<<endl;
return;
}
if(l < h){
int low = l;
int high = h;
int temp = a[low];
while(low < high){
while(low < high){
if(a[high] >= temp){
high--;
}else{
a[low] = a[high];
low++;
break;
}
}
while(low < high){
if(a[low] <= temp){
low++;
}else{
a[high] = a[low];
high--;
break;
}
}
}
a[low] = temp;
quickSort(a,l,high-1);
quickSort(a,high+1,h);
}
}
int main(){
int a[] = {5,3,6,3,1,4,7,2};
quickSort(a,0,7);
for(int i = 0; i < 8; i++){
cout<<a[i]<<" ";
}
}
上述代码选取第一个元素为枢纽,若要随机选取枢纽元素,则首先将选取的元素与第一个元素对换即可。
2.3选择排序
简单选择排序、堆排序
2.3.1简单选择排序
#include "stdafx.h"
#include <iostream>
using namespace std;
void simpleSelectSort(int* a, int n){
if(a == NULL || n <= 0){
return;
}
int i,j,min,temp;
for(i = 0; i <= n -2; i++){
min = i;
for(j = i; j <= n-1; j++){
if(a[j] < a[min]){
min = j;
}
}
temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
int main(){
int a[] = {5,3,6,3,1,4,7,2};
simpleSelectSort(a,8);
for(int i = 0; i < 8; i++){
cout<<a[i]<<" ";
}
}
2.3.2堆排序
思想:
(1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆;
(2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn);
(3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区
(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
#include "stdafx.h"
#include <iostream>
using namespace std;
//a[s..m]中除a[s]之外均满足堆的定义,
//本函数调整a[s],使a[s..m]成为一个大顶堆
void heapAdjust(int* a, int s, int m){
int temp = a[s];
for(int i = 2 * s + 1; i <= m; i = i * 2 + 1){
if(i < m && a[i] < a[i+1])
i++;
if(temp >= a[i])
{
break;
}else{
a[s] = a[i];
s = i;
}
}
a[s] = temp;
}
int main(){
int a[] = {5,3,6,3,1,4,7,2};
int n = 8;
for(int i = n/2-1; i >= 0; i--){
heapAdjust(a,i,n-1);
}
for(int i = 0; i < 8; i++){
cout<<a[i]<<" "<<endl;
}
int temp;
for(int i = n-1; i >= 1; i--){
temp = a[0];
a[0] = a[i];
a[i] = temp;
heapAdjust(a,0,i-1);
}
for(int i = 0; i < 8; i++){
cout<<a[i]<<" ";
}
}
2.4归并排序
与快速排序和堆排序相比,归并排序的最大特点是,它是一种稳定的排序方法。但在一般情况下很少利用2-路归并排序法进行内部排序。实现归并排序需和待排记录等数量的辅助空间,时间复杂度为O(nlogn)。
#include "stdafx.h"
#include <iostream>
using namespace std;
void merge(int a[],int start,int mid,int end,int temp[])
{
int i = start;
int j = mid +1;
int k = start;
while(i<=mid&&j<=end)
{
if(a[i]<=a[j])
{
temp[k++]=a[i++];
}
else
{
temp[k++]=a[j++];
}
}
while(i<=mid)
{
temp[k++]=a[i++];
}
while(j<=end)
{
temp[k++]=a[j++];
}
for(int m=start;m<=end;m++)
{
a[m] = temp[m];
}
}
void mSort(int a[],int start,int end,int temp [])
{
if(start<end)
{
int mid = (start+end)/2;
mSort(a,start,mid,temp);
mSort(a,mid+1,end,temp);
merge(a,start,mid,end,temp);
}
}
void mergeSort(int a[],int n)
{
int* temp = new int[n];
mSort(a,0,n-1,temp);
delete [] temp;
}
int main()
{
int a[] = {3,5,7,2,1,6};
int n = 6;
mergeSort(a,n);
for(int i=0;i<n;i++)
{
cout<<a[i]<<" ";
}
}
排序方法 | 平均情况(时间复杂度) | 最坏情况 | 最好情况 | 空间复杂度 | 稳定性 |
直接插入排序 | n^2 | n^2 | n | 1 | 稳定 |
折半插入排序 | n^2 | 1 | 稳定 | ||
希尔排序 | 1 | 不稳定 | |||
冒泡排序 | n^2 | n^2 | n | 1 | 稳定 |
快速排序 | n*logn | n^2 | n*logn | logn | 不稳定 |
简单选择排序 | n^2 | n^2 | n^2 | 1 | 不稳定 |
堆排序 | n*logn | n*logn | n*logn | 1 | 不稳定 |
归并排序 | n*logn | n*logn | n*logn | n | 稳定 |
1.折半插入排序的元素比较次数由于采用了折半查找,相对与直接插入排序减少了,时间复杂度为n*logn,但是元素的移动次数并未减少,因此时间复杂度仍未n^2。
2.希尔排序是不稳定的。(希尔排序的时间复杂度是O(n的1.25次方)~O(1.6n的1.25次方) 这是一个经验公式,好像没人解释过,就是一句经验得出的。
希尔排序的分析是一个复杂的问题,以为它的时间是所取“增量”序列的函数,这涉及到一些数学上尚未解决的难题。 数据结构书上这么说的 )
3.快速排序会递归log(n)次,每次对n个数进行一次处理,所以他的时间复杂度为n*log(n)。
4.简单选择排序稳定性:举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
5.冒泡排序和简单选择排序的区别:冒泡算法,每次比较如果发现较小的元素在后面,就交换两个相邻的元素。而选择排序算法的改进在于:先并不急于调换位置,先从A[1]开
始逐个检查,看哪个数最小就记下该数所在的位置P,等一躺扫描完毕,再把A[P]和A[1]对调,这时A[1]到A[10]中最小的数据就换到了最前面的位置。
所以,选择排序每扫描一遍数组,只需要一次真正的交换,而冒泡可能需要很多次。比较的次数是一样的。
6.堆排序在最坏的情况下时间复杂度也为n*logn,相对于快速排序来说这是堆排序的最大优点。
附:冒泡排序最坏情况是把顺序的排列变成逆序,或者把逆序的数列变成顺序。在这种情况下,每一次比较都需要进行交换运算。
举个例子来说,一个数列 5 4 3 2 1 进行冒泡升序排列,第一次大循环从第一个数(5)开始到倒数第二个数(2)结束,比较过程:先比较5和4,4比5小,交换位置变成4 5 3 2
1;比较5和3,3比5小,交换位置变成4 3 5 2 1……最后比较5和1,1比5小,交换位置变成4 3 2 1 5。这时候共进行了4次比较交换运算,最后1个数变成了数列最大数。
第二次大循环从第一个数(4)开始到倒数第三个数(2)结束。进行3次比较交换运算。……所以总的比较次数为 4 + 3 + 2 + 1 = 10次。
对于n位的数列则有比较次数为 (n-1) + (n-2) + ... + 1 = n * (n - 1) / 2,这就得到了最大的比较次数。而O(N^2)表示的是复杂度的数量级。
计算时间复杂度时要找基本操作,比如在冒泡排序中,比较是基本操作,而交换不是,因为比较每次都需要做,而交换不一定。