数据结构——排序(一)

数据结构——排序(二)

一、排序

1.1 直接插入排序

对数组arr[]排序,arr[0]作为哨兵,第一个元素arr[1]直接作为有序序列,从第2个元素依次往后获取,获取一个无序序列的arr[i]元素后复制放置到哨兵位置arr[0],依次扫描有序序列确定该元素的位置为arr[k],则将arr[k…i-1]元素都向后移一个位置,再将该元素arr[0]插入在arr[k]的位置。
空间复杂度O(1),时间复杂度O(n^2)

//直接插入排序
void InsertSort(ElemType arr[] , int n){       //ElemType数组的类型,n为数组元素数量
  int i,j;                                     //i:扫描待排的无序序列,j:扫描有序序列
  for(i=2; i<=n; i++){
    if(arr[i] < arr[i-1]){    //若待排元素比有序序列最大元素小
      arr[0] = arr[i];        //复制放置到哨兵位置arr[0]
      for(j=i-1; arr[j] > arr[0]; j--){  //从后往前扫描,若比待排元素arr[0]大的有序序列元素
        arr[j+1] = arr[j];                  //向后移一个位置
      }
      arr[j] = arr[0];    //将该待排元素arr[0]复制到插入位置
    }
  }//for i
}     

1.2 折半插入排序

对数组arr[]排序,arr[0]作为哨兵,第一个元素arr[1]直接作为有序序列,从第2个元素依次往后获取,获取一个无序序列的arr[i]元素后复制放置到哨兵位置arr[0],
折半扫描查找该元素在有序序列的位置k,再统一将arr[k…i-1]元素都向后移一个位置,再将该元素arr[0]插入在arr[k]的位置。
空间复杂度O(1),时间复杂度O(n^2)

//折半插入排序
void Bi_InsertSort(ElemType arr[] , int n){       //ElemType数组的类型,n为数组元素数量
  int i,j,low,high,mid;                                        
  for(i=2; i<=n; i++){
    arr[0] = arr[i];        //复制放置到哨兵位置arr[0]
    low=1;high=i-1;        //折半查找的范围
    while(low <= high){    //折半查找
      mid = (low+high)/2;
      if(arr[mid] > arr[0]){  //若中间值比待排元素arr[0]大,扫描序列左边子表
        high=mid-1;                  //high=左边子表最大元素
      }
      else{                          //扫描序列右边子表
        low=mid+1;                  //low=右边子表最小元素
      }
      
      for(j=i-1; j>=high+1; j--){  //从后往前,有序序列元素high位置<=该元素
        arr[j+1] = arr[j];                  //向后移一个位置
      }
      arr[j] = arr[0];    //插入位置
    }
  }//for i
}     

1.3 希尔排序(缩小增量排序)

取一个增量d1(常用d1=n/2),将隔d1的元素组成一组(即arr[1,i+d1,i+2d1……]),进行直接插入排序,再取增量d2<d1(常用di=di-1/2)进行直接插入排序……直到di=1,进行最后一次直接插入排序。
空间复杂度O(1)

//希尔排序
void Shell_InsertSort(ElemType arr[] , int n){       //ElemType数组的类型,n为数组元素数量
  int i,j,d;                        //d:增量
  for(d=n/2; d>=1; d=d/2){          //增量变化
    for(i=d+1; i<=n; i++){          //从每组的第二个元素开始d+1, i++扫描下一组
      //内含直接插入排序
      if(arr[i] < arr[i-d]){    //若待排元素比组内有序序列最大元素小
        arr[0] = arr[i];        //复制放置到哨兵位置arr[0]
        for(j=i-d; j>0&&arr[j] > arr[0]; j-=d){  //从后往前扫描,若比待排元素arr[0]大的有序序列元素
          arr[j+d] = arr[j];                  //向后移一个位置
        }
        arr[j] = arr[0];    //将该待排元素arr[0]复制到插入位置
      }
    }//for i
  }
}     

1.4 冒泡排序

对数组A[]排序,从后往前(从前往后),按从小到大(从大到小)顺序,一次比较两个元素,扫描数列一轮轮是重复地进行,直到没有再需要交换(前一次已经没有进行交换/已经扫描到数组大小n最后一次),也就是说该数列已经排序完成。
越小(大)的元素会经由交换慢慢“冒”到数列的顶端(末端)。
空间复杂度O(1),时间复杂度O(n^2)

//冒泡排序(从后往前,从小到大)
void BubbleSort(int A[],int n) {
	int i, j = 0;
	bool flag;             //flag:标记这一轮扫描数组是否进行了交换,若没有进行交换则为false,说明数组已经排序完成
	for (i = 0; i < n; i++) {
		flag = false;
	    //由于n为数组大小,数组内数据为a[0]-a[n-1],所以j=n-1才是从最后一个数据开始扫描
		for (j = n-1; j > i; j--) {                  
			if (A[j] < A[j - 1]) {                 //若为逆序(与你所要的顺序对比)
				swap(A[j], A[j - 1]);              //swap交换两个数据的位置
				flag = true;                       //有进行交换
			}
		}
		if (flag == false) {
			return;
		}
	}
}

运行结果:

1.4.2 双向冒泡排序

从正反两个方向交替进行扫描,即第一趟把最大元素放在最尾,第二趟把最小元素放在最前。

//双向冒泡排序(从小到大)
void Bidire_BubbleSort1(int A[], int n) {
	int low = 0;
	int high = n - 1;
	int i, j = 0;
	bool flag;
	while (low < high) {
		flag = false;
		for (i = low; i < high; i++) {
			if (A[i] > A[i + 1]) {
				swap(A[i], A[i + 1]);
				flag = true;
			}
		}
		high--;
		cout << endl << "从后往前,high=" << high << endl;
		outArr(A, n);
		if (flag == false) {
			return;
		}
		flag = false;
		for (j = high; j > low; j--) {
			if (A[j] < A[j - 1]) {
				swap(A[j], A[j - 1]);
				flag = true;
			}
		}
		low++;
		cout << endl << "从前往后,low=" << low << endl;
		outArr(A, n);
		if (flag == false) {
			return;
		}
	}

}

运行结果:
在这里插入图片描述
在这里插入图片描述

中间输出:
在这里插入图片描述

1.5 快速排序

对数组A[]排序,选择一个元素作为基准/枢轴pivot(一般选首元素),将A[]以小于、大于等于pivot分为两部分,再分别选pivot,直到每部分内只有一个元素或没有元素。
空间复杂度O(log2n) ~ O(n) ,时间复杂度O(nlog2n) ~ O(n^2)

/**进行划分:
将小于枢轴pivot的划到pivot左边,其余在右边
将枢轴pivot放到该部分数组中对应的最终位置
**/
int Partition(int A[],int low,int high) {
	int i, j;
	int pivot = A[low];      //一般选择首元素为枢轴,保存在pivot,视A[low]位置为空
	while (low < high) {     //当被划分的部分没有元素,不需要再次划分
		for (j = high; j > low; j--) {    //从后往前扫描,到与low位置重合,则为pivot的位置
			if (A[j] >= pivot) {          //若扫描数>=pivot,则保留原位置,继续往前扫描
				high--;
			}
			else {
				A[low] = A[high];         //若扫描数<pivot,则放置到前面空出来的low位置
				break;                    //跳出high部分扫描,(若有元素)进入low部分往后扫描
			}
		}
		for (i = low; i < high; i++) {   //从前往后扫描,到与low位置重合,则为pivot的位置
			if (A[i] < pivot) {          //若扫描数<pivot,则保留原位置,继续往后扫描
				low++;
			}
			else {
				A[high] = A[low];        //若扫描数>=pivot,则放置到后面空出来的high位置
				break;                   //跳出low部分扫描,(若有元素)进入high部分继续往前扫描
			}
		}
	}
	A[low] = pivot;
	return low;
}


void QuickSort1(int A[],int low,int high) {
	if (low < high) {
		int pivot_pos = Partition(A, low, high);    //找到枢轴,进行一次划分
		QuickSort1(A, low, pivot_pos-1);            //扫描枢轴左半部分
		QuickSort1(A, pivot_pos+1, high);           //扫描枢轴右半部分
	}
}

优化:枢轴pivot(一般选首元素)——>选择首、尾、中元素的中间数为pivot,或随机选择元素为pivot,以降低遇到最坏情况的概率。最坏情况指:两部分分配特别不均匀,一般是基本有序或基本逆序的情况下。
方法1:

//快排使用的取中间值为枢轴
void sel_midNumber1(int A[], int low, int high) {
	int mid = (low + high) / 2;
	if (A[low] > A[mid]) {
		swap(A[low], A[mid]);
	}
	if (A[mid] > A[high]) {
		swap(A[mid], A[high]);
	}
	return ;
}

在划分函数Partition()中修改的部分:

	int i, j;
	if (high - low > 2) {
		sel_midNumber1(A, low , high);
		swap(A[low], A[(low + high) / 2]);           //将中间数放在最前面作为枢轴
	}
	int pivot = A[low];      //选择被放在首元素的中间数为枢轴,保存在pivot,视A[low]位置为空

方法2:找中间值的时候不进行交换

int sel_midNumber2(int A[], int low, int high) {
	int mid = (low + high) / 2;
	int k;
	k = A[low] < A[mid] ? 
	(A[mid] < A[high] ? mid : high) : 
	(A[mid] < A[high] ? (A[low] < A[high] ? low : high) : mid);
	return k;
}

在划分函数Partition()中修改的部分:

	if (high - low > 2) {
		int k=sel_midNumber2(A, low , high);

		swap(A[low], A[k]);           //将中间数放在最前面作为枢轴
	}
	int pivot = A[low];      //选择被放在首元素的中间数为枢轴,保存在pivot,视A[low]位置为空

二、对比

稳定性:相同元素的相对位置在排序后是否改变

空间复杂度时间复杂度最好时间复杂度最坏时间复杂度排序类型稳定性每趟排序都有一个元素被放到最终位置适用于
直接插入排序O(1)O(n2)O(n)O(n2)插入×顺序存储、链式存储的线性表,数据量n较小,基本有序
折半插入排序O(1)O(n2)O(n2)插入×顺序存储、链式存储数据量n较小
希尔排序O(1)O(n2)O(n1.3)O(n2)插入××顺序存储的线性表
冒泡排序O(1)O(n2)O(n)O(n2)交换顺序存储、链式存储 ,基本有序
快速排序O(n)O(nlog2n)O(nlog2n)O(n2)交换×顺序存储,数据量n较大

三、测试

int main() {
	int A[10] = { 3,55,66,22,77,88,12,99,30,23 };
	int n = 10;
	cout << "The original array:" << endl;
	outArr(A, n);
	BubbleSort1(A, n);
	cout << "The array ofter Bubble Sort1:" << endl;
	outArr(A, n);
	return 0;
}
void outArr(int A[], int n) {
	int i = 0;
	for (i = 0; i < n; i++) {
		cout << A[i] << "\t";
		if ((i + 1) % 5 == 0 && (i + 1) / 5 > 0) {
			cout << endl;
		}
	}
	cout << endl;
}

四、题目

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值