查找和排序

本文详细介绍了数据结构中的线性表、树表、哈希表的查找方法及排序算法如插入排序、快速排序等,并对比了各种算法的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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^2n^2n1稳定
折半插入排序n^2  1稳定
希尔排序   1不稳定
冒泡排序n^2n^2n1稳定
快速排序n*lognn^2n*lognlogn不稳定
简单选择排序n^2n^2n^21不稳定
堆排序n*lognn*lognn*logn1不稳定
归并排序n*lognn*lognn*lognn稳定

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)表示的是复杂度的数量级。

计算时间复杂度时要找基本操作,比如在冒泡排序中,比较是基本操作,而交换不是,因为比较每次都需要做,而交换不一定。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值