外部排序.

对内部排序的分析

	方法     时间负责度  空间复杂度    稳定性
	直接插入   n^2             1        稳定
	折半插入   n^2             1        不稳定/稳定(一般)
	希尔排序   nlog2n                   不稳定
	二路插入   n^2
	直接选择   n^2                         不稳定
	二路选择   n^2                         不稳定
	冒泡       n^2
	鸡尾酒     n^2
	堆         nlog2n                      不稳定
	二叉树     nlog2n                      不稳定
	快速       nlog2n          nlog2n  o(n)书上空间  不稳定
	归并       nlog2n          nlog2n      稳定
	基数       d(n+rd)         r+n         稳定
	计数       n               max-min+1   没有稳不稳定这个说法  因为根据下标来

外部排序

	内存有限,数据量过大,数据不能一次性加载到内存中
	数据保存在外存中(硬盘,文件里面)上

	限制外部排序效率的因素是IO(读写的速率)的效率
	所以提高外部排序的效率的方法是减少IO次数 
	寄存器>cache>内存>固态>机械
	
思路:
	二路归并(又或者说是m路归并)
	第一次先按块加载进来,使用内部排序中的任意一种进行排序,再写回到原文件

外部排序读写的次数与归并的趟数成正比
每归并一趟,把所有的数据加载进来,并且写回来
总共进行多少趟归并  log2(n/k)   logm r   r=n/k    m就是归并路数,2即路数为2
n是总的数据个数,k是每个内存块中存储数据的个数
n/k == 数据块  初始归并段的数量r,初始归并不算入次数

提高外部排序的效率:

1.多路归并,增加归并路数  有败者树这个概念
败者树的作用:减少多路归并时比较的次数
除了第一次比较以外,最多需要logm次比较  
因为第一次选出后,其他队的比较结果其实还是和上一次一样的,
除了赢的那一队的第一位胜者出,该队下一个队员和之前比较的那组再进行比较,
所以其实少了最基础的其他队比较的环节,因此减少了比较次数。

2.减少初始归并段  (即内存扩大,数据存储的个数增多)
用   置换选择排序  来初始化归并段

所以我们有    最佳归并树    这个概念 (根据初始归并段构造最佳归并树,减少读写磁盘次数)
用置换-选择排序, 初始化的每个归并段中数据的个数不一样
用这些归并段来构造一个m叉树,要使得读写次数最少
需要构造一棵最优m叉树,即最佳归并树
例如归并段:
9 30 12 18 3 17 2 6 24  这个是每组里的个数,总共9组
三路平衡三叉树的次数:
(9+ 30 +12+ 18+ 3+ 17+ 2 +6 +24)*2*2=484
第一个2是路径长度,路径长度不算自己所在那层


虚段:数量为0
为了构造m叉这样的最佳归并树,可能需要增加虚段(0);
因为归并段和归并路数相关

9 12 18 3 17 2 6 24        8,3路归并时,8个段没法构成三叉最优归并树   
9 12 18 3 17 2 6 24 0      9,所以补了一个虚段


   5
0  2  3
0就是虚段

其实是哈夫曼树的知识点(WPL带权路径最短 )
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

归并段是否补充及补充多个虚段的推解

假设对m个归并段,进行k路归并,使读写次数最少,得构造最优归并树
需要补充多少个虚段?
9个归并段 3路归并
8个归并段 3路补充一个虚段
7个归并段 3路不用补
10个  4路不用补
11个  4路补
所以
(m-1)%(k-1)==0不需要补充虚段   m是叶子结点的个数。除了叶子结点以外,所有结点需要k个子结点  
n个度为k的结点,m个度为0的结点
结点个数=总度数+1

 nk+1:这是所有的结点数    nk+1=n+m;

(k-1)n=m-1;  //n肯定是整数  n=(m-1)/(k-1) 

如果要补 要补k-(m-1) % (k-1)-1个虚段
即   k-1 – (m-1)%(k-1)

推证:
如果x/y!=0;
即x%y有余数
(x-x%y+y)%y==0
(x+(y-x%y))%y==0;
所以补了y-x%y这些虚段
所以是k-1-(m-1)%(k-1)

搜索

查找表
静态查找表:若对查找表只作查找和检索的操作

1.顺序查找
这个顺序查找的写法比较新颖
在这里插入图片描述

动态查找表:除了查找和检索的操作,进行插入和删除

2.有序表查找
递推二分查找

int bin_search(int arr[],int n,int key){
	int left = 0,right = n-1;
	while(left<=right){
		int mid = (left+right)/2;
		if(arr[mid] == key){
			while(arr[mid-1]==arr[mid])
				mid--;
			return mid;
		}
		if(key < arr[mid]){
			right = mid - 1;
		}else{
			left = mid + 1;
		}
	}
	return -1;
}

递归二分查找

int binSearch(int arr[],int left,int right,int key){
	if(left>right)
		return -1;
	int mid = (left+right)/2;
	if(arr[mid]==key)
		return mid;
	if(key<arr[mid])
		return binSearch(arr,left,mid-1,key);
	return binSearch(arr,mid+1,right,key);	
}

int bin_search(int arr[],int n,int key){
	return binSeach(arr,0,n-1,key);
}
ASL:
查找算法衡量好坏的依据为:查找成功时,查找的关键字和查找表中的数据元素中进行过比较的	
个数的平均值,称为平均查找长度(Average Search Length,用 ASL 表示)
	1 5 9 2 4

ASL = (1+2+3+4+5 +5*5)/10
	

ASL成功 = 1/5 + 2/5 + 3/5 + 4/5 + 5/5
ASL失败 = 5

1查找了4次,5查找了2次,9查找了3次,2次了1次,4查找了0次
10次  4/10
ASL成功 = 1*4/10 + 2*2/10 + 3*3/10 + 4*1/10 + 5*0/10
        = (4 + 2*2 + 3*3 + 4*1 + 5*0)/10  

ASL=概率*比较次数


单向链表不能用bsearch,因为链表不连续   这些都要连续的	
man bsearch  //库中包含了二分查找这个函数
void *bsearch(const void *key,const void *base,size_t nmemb,size_t size,
int (*comp)(const void *,const void *));

折半查找判定树

PH值取最小的二叉树为静态最优查找树
带权内路径长度之和PH(和平均查找长度成正比)值

ASL=(1+2+3+4+5+5*5)/10

B- B+ 树

B树的深度要比表面上给的+1,因为图上表示的最后一层下面还有看不见的NULL层

在这里插入图片描述

m阶B-插入:
	插入到叶子结点,如果叶子结点关键字等于m个,需要分裂
	把[m/2]关键字上提,如果上提导致父结点关键字等于m个
	需要继续向上分裂

m阶B-删除:
	删除非叶子结点关键字,需要用左边树中最大值来替换
	删除左边树中最大值(叶子结点)
	删除之后,结点关键字个依然还够[m/2]个,不需要处理
	如果个数不够[m/2],则考虑向左或者向右借
	如果左右不够借的情况,向左/右合并

B+树

在这里插入图片描述
在这里插入图片描述

一颗 m 阶的 B+树和 m 阶的 B-树的差异在于:

有 n 棵子树的结点中含有 n 个关键字;
在上一节中,在 B-树中的每个结点关键字个数 n 的取值范围为⌈m/2⌉ -1≤n≤m-1,而在
B+树中每个结点中关键字个数 n 的取值范围为:⌈m/2⌉≤n≤m。
所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身
依关键字的大小自小而大顺序链接。
所有的非终端结点(非叶子结点)可以看成是索引部分,结点中仅含有其子树(根结点)中的最
大(或最小)关键字。

哈希表:(又称散列表,是一种数据结构)

	特点是:数据元素的关键字与其存储地址直接相关,通过散列函数(哈希函数)f(key)
映射到其存储地址哈希查找理想的情况是希望不经过任何比较,一次存取便能得到所查记录,但实际的情况是,
对不同的关键字可能得到同一哈希地址,即key1!=key2,而f(key1) =f(key2),这种现象称为冲突(collision)
具有相同函数值的关键字(key1,key2)对该哈希函数来说称做同义词(synonym)。

冲突越多,查找效率越低
处理冲突的方法有:
	1.开放定址法
	2.再哈希法
	3.链地址法
	4.建立一个公共溢出区

在这里插入图片描述
在这里插入图片描述

哈希函数H(key)=key MOD 13
哈希查找效率最高  O(1) 没有冲突的情况下	


线性探测再散列方法构造哈希表
%求余,被占往后放
失败次数
(0+12+11..+1)/13
成功次数

索引顺序表查找

在这里插入图片描述

朴素查找(BF算法):在str中查找childstr,如果找到返回下标位置,没有返回-1


#include<stdio.h>
#include<string.h>

int find(const char *str,const char *childstr){
	int i;
	int lenchild=strlen(childstr);
	int lenstr=strlen(str);
	for(i=0;i<lenstr;i++){
		int j=0;
		int k=i;
		if(str[k]==childstr[j]){
			while(str[k++]==childstr[j++]);
		}
		j--;
		if(j==lenchild){
			return i;
		}
	}
	return -1;
}//这是自己写的方法

int find(const char *str,const char *childstr){
	int i,j=0;
	for(i=0;str[i]!='\0'&&childstr[j]!='\0';){
		if(str[i]==childstr[j]){//如果相等继续比较下一个 
			i++;
			j++;
		}else{//如果不相等,str回溯到 j 个字符串之前+1 
			i=i-j + 1;//不相等的时候 主串回溯  
			j=0;
		}
	}
	if(childstr[j]=='\0') return i-j;
	return -1;
}//这是新方法

//for(i=0;str[i]!=’\0’;i++)
//if(strncmp(str+i,childstr,len)==0 return i      //这是如果可以使用strncmp的关键步骤
//这个时间复杂度O(nm)   n str的长度   m childstr的长度
int main(){
	int ret=find("asdafa","af");
	printf("%d\n",ret);
	return 0;
}

在这里插入图片描述

KMP算法

快速模式匹配算法,简称 KMP(D.E.Knuth,J.H.Morris和V.R.Pratt) 算法
朴素查找会回溯,时间复杂度为O(nm)   n为主串长度,m为子串长度
而kmp算法时间复杂度为O(n+m), 主串不回溯  ,仅调整j的位置,即子串的位置,kmp会算定j回退的距离

在这里插入图片描述

//kmp  O(n+m)  主串不回溯   调整j的位置 
//当childstr[j] 和 主串不相等时  j退回到哪一个字符进行比较
void getNext(const char *pattern,int next[]){
	next[0] = -1;
	int len = strlen(pattern);
	int j=0,k=-1;//next[j]   pattern[0,j-1] 最大重复子串 
	while(j<len-1){
		if(k==-1 || pattern[j] == pattern[k]){
			//next[++j] = ++k;
			//优化next数组 
			if(pattern[++j] == pattern[++k]){
				next[j] = next[k];
			}else{
				next[j] = k;
			}
		}else{//前缀的最后一个字符和后缀最后一个字符不相等 
			k = next[k];
		}
	} 
} 
//str主串  pattern模式串 
int kmp(const char *str,const char *pattern){
	int i=0,j=0;
	int next[strlen(pattern)];
	getNext(pattern,next);
	while(str[i]!='\0'&&pattern[j]!='\0'){
		//j==-1 第一个字符就不相等 只能改变j不能改变i 
		if(j==-1||str[i]==pattern[j]){
			++i;
			++j;
		}else{
			j = next[j];//j回退到指定的位置继续比较 
		}
	}
}

这个版本的KMP算法对getNext函数的核心算法会浪费时间的问题已经进行了优化

kmp getNext()核心算法推解

模式串移动距离的判断每次模式匹配失败后,计算模式串向后移动的距离是 KMP 算法中的核心部分。

ABCAB   D 其实是D这个位置不同
前缀    但是最后那个不取   [0,j-1
A
AB
ABC
ABCA
后缀    最前那个不取  
BCAB
CAB
AB
B
前缀和后缀相等的最大长度
AB所以是2

全排列

全排列 1 2 3 4   4*3*2*1
假设数组 总共n个数据,现在还要m个数全排列
void showArr(int arr[],int n){
	int i;
	for(i=0;i<n;i++){
		printf("%d ",arr[i]);
	}
	printf("\n");
}
void swap(int *pa,int *pb){
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}
//数组总共n个数据  m个数需要进行全排列 
void showAll(int arr[],int n,int m){
	if(m==1){
		showArr(arr,n);
	}else{
		int i;
		for(i=m-1;i>=0;i--){
			swap(&arr[i],&arr[m-1]);//固定最后一个值 
			showAll(arr,n,m-1);//把剩下的m-1个数再进行全排列
			swap(&arr[i],&arr[m-1]);//恢复原样,不影响下一种情况 
		} 
	}
}
//对n个数进行全排列 
void pailie(int arr[],int n){
	//   (1 2 3全排列)   4
	//   (1 2 4全排列)   3 
	//   (1 3 4全排列)   2 
	//   (2 3 4全排列)   1 
	showAll(arr,n,n);
}
//全排列   1 2 3 4   4*3*2*1   10 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值