zoukankan      html  css  js  c++  java
  • 查找算法

    1. 基本概念

    查找结构通常有四种操作:查询某个特定元素是否在表中,检索满足条件的某个特定元素的各种属性,在查找表中插入某一数据元素,从查找表中删除某个元素

    • 只涉及前两种操作的称为静态查找,包括顺序查找,二分(折半)查找,散列查找等,涉及到后面两种操作的称为动态查找,包括二叉排序树查找,散列查找等
    • 平均查找长度:所有查找过程中关键码比较次数的平均值,衡量查找算法效率的主要指标

    2. 折半查找(二分查找)

    • 二分查找仅适用于事先已经排序过的线性表顺序存储结构(需要方便定位查找区域,存储结构具有随机存储的特点)
    • 二分查找的时间复杂度为O(log2N),查找成功或者查找不成功,最坏情况下需要log2N + 1次检索

    3. 键树

    键树称为数字查找树,是度大于等于2的书,书的每个结点包含的是组成关键字的符号,如果关键字是数值,则结点包含一个数位,如果关键字是单词,则结点包含一个字母字符,如下图所示。

    键树.png

    3.1 双链树(树的孩子-兄弟链来表示键树)

    每个Node有三个域:

    • symbol域:存储关键字的一个字符
    • son域:存储指向第一个子树的根针
    • brother域:指向右兄弟指针
      查找过程是,从根结点出发,顺着son查找,如果相等,继续下一个son。否则沿着brother查找。直到到了空指针为止。此时若仍未完成key的匹配,查找不成功。

    双链表.png

    3.2 字典树

    字典树,又叫做Trie树,单词查找树,或者前缀树,是一种用于快速检索的多叉树结构,树的每个结点包含d个指针域,d是关键字符的基,比如英文字母的字典树是26叉树,数字字典树是10叉树

    字典树1.png

    字典树2.jpg

    • Trie树基本性质:

      1. 根结点不包含字符,除了根结点之外每个结点包含一个字符
      2. 从根结点到某一个叶子节点,路径上经过的字符连接起来,为一个字符串
      3. 每个结点所包含的子结点包含的字符串不同
    • Trie树通过字符串的公共前缀来降低开销,它的优点是最大限度减少无谓的字符串比较,其典型应用是用于统计和排序大量字符串

    • Trie的缺点是:如果存在大量字符串,而这些字符串基本没有公共前缀,那么Trie树将非常消耗内存。

    • Trie树的实现:

    #include<iostream>
    #include<cstdlib>
    using namespace std;
    
    const int branchNum = 26;
    struct Trie_node{
    	bool isStr; // 记录此处是否构成一个串
    	Trie_node * next[branchNum]; //指向各个子树的指针
    	
    	// 初始化
    	Trie_node() :isStr(false){ memset(next, NULL, sizeof(next)); }
    };
    
    class Trie{
    private:
    	Trie_node *root;
    public:
    	Trie(){ root = new Trie_node(); }
    	void insert(const char * str);
    	bool search(char * str);
    	void deleteTrie(Trie_node * root);
    	Trie_node * getTrie(){ return root; }
    };
    
    void Trie::insert(const char * str){
    	Trie_node * location = root;
    	while (*str){
    		// 如果不存在则建立结点
    		if (location->next[*str - 'a'] == NULL){
    			Trie_node *temp = new Trie_node();
    			location->next[*str - 'a'] = temp;
    		}
    		location = location->next[*str - 'a'];
    		// 每插入一步,相当于新串路过,指针移动
    		str++;
    	}
    	location->isStr = true;//标记一个串
    
    	// Trie *temp = (Trie *) malloc(sizeof(Trie));
    	// for(int i =0;i<26,i++)
    	// temp->next[i] = NULL:
    }
    
    
    bool Trie::search(char * str){
    	Trie_node * location = root;
    	while (*str && location){  // *str!=''
    		location = location->next[*str - 'a'];
    		str++;
    	}
    	return (location != NULL && location->isStr);
    }
    
    void Trie::deleteTrie(Trie_node *root){
    	for (int i = 0; i < branchNum; i++){
    		if (root->next[i] != NULL)
    			deleteTrie(root->next[i]);
    	}
    	delete(root);
    }
    
    int main(){
    	char *str = "abcdefg";
    	Trie trie;
    	trie.insert(str);
    	if (trie.search(str)) cout << "true";
    	system("pause");
    	return 0;
    }
    
    • Trie树的应用:
      1. 给定一个单词a,如果通过交换字幕的顺序可以得到另外的单词b,那么称a和b是是兄弟单词,现在要求给一个字典,用户输入一个单词,可以根据字典找到该单词的兄弟单词,要求时间和空间效率尽可能高。
        答:解法一:hash_map 和链表,定义一个ID,使得兄弟单词有相同的id,不是兄弟单词有不同的id,这个id可以是将单词从小到大排序后作为其ID,也可以是将单词各个字母对应一个质数,将质数相乘当做hash id。创建一个hash_map,它的key为单词的id,value为兄弟单词链表的起始地址。所有的兄弟单词存放在一个链表中。当需要找到该兄弟单词时,只需要计算单词id,然后到map中找到对应的链表即可。
        解法二:利用Trie树,单词插入Trie树前,先按照字母排序,将排序后的字母放入Trie树,在树的结点中增加一个vector,用于记录所有的兄弟单词

      2. 数据文件A:1000万条关键词,数据文件B:关键词与ID的对应表,100万条左右,现在将A中关键词替换为ID,可用内存为1GB,硬盘不限
        答:使用文件B生成Trie树,然后用Trie树实现关键词对ID 的快速查找,Trie_node结点中包含ID信息,主要是实际应用中关键词之间可能有很多前缀相同现象,所以空间耗费不会很高

    • 参考:
      1. 海量数据处理之Tire树(字典树)
      2. 字典树(Trie树)实现与应用

    4.后缀树和后缀数组(suffix tree)

    5. 哈希表

    • 哈希表的设计目的:空间换取时间,基于快速存取的角度设计,根据关键字直接访问的数据结构,通过某种规则将关键字映射到数组某个位置,这个映射规则称为哈希函数/散列函数
    • 哈希冲突:不同的关键字通过哈希函数计算得到了相同的数组下标,在设计hash函数应该尽量避免这样的冲突,同时还要设计处理好可能产生的冲突。
    • 哈希函数:如果两个hash值不同,那么对应的这两个hash值的原始输入是不相同的,但是两个hash值相同,原始输入的两个key值不一定是相同的。
      1. 常用hash函数:直接定址法,数字分析法,平方取中法,除留余数法,折叠法
      2. MD4、MD5(更安全)、SHA-1;(用于文件检验,数字签名,鉴权协议)
    • 处理冲突的方法:链地址法(同义词存储在同一个线性链表中),开放定址法,再散列法(发生冲突时利用另一个哈希函数重新计算),建立一个公共溢出区(填入溢出表)

    6. 一致性哈希

    • 集群问题(待续)

    7. 海量数据处理

    7.1 hash映射-分治处理

    对大文件处理时,若文件过大,无法一次性读入内存,将hash映射将文件元素映射到不同小文件中,在依次处理各个小文件,最后合并处理结果。
    例子:a、b文件,各存放50个url,请找出a、b共同的URL?
    答:遍历a,hash(url)%1024,将a分别存放在1024个文件中,对b进行同样操作,处理后,**所有可能相同的url都在对应的小文件中,如a0对应b0,然后分别对小文件进行遍历搜索处理等即可

    7.2 Top K问题

    • 常见问题:最大的k个数或者最小的k个数
    • 如果数据能够一次性读入内存,快排的一次排序,时间复杂度为O(n)
    • 如果海量数据,我们通常使用,(最大k个数为小根堆,最小K个数使用大根堆)

    【编程之美】读书笔记:寻找最大的K个数
    《编程之美》——寻找最大的K个数

    // 快排 我们基于数组的第K个数字来调整时,最小的k个数
    void getleastNumber(int *input, int n, int *output, int k){
    	if (input == NULL || output == NULL || k>n || n <= 0 || k <= 0)
    		return;
    
    	int start = 0;
    	int end = n - 1;
    	int index = Partition(input,  start, end);
    	while (index != k - 1){
    		if (index > k - 1){
    			end = index - 1;
    			// 一趟快排
    			index = Partition(input, start, end);
    		}
    		else{
    			start = index + 1;
    			index = Partition(input,  start, end);
    		}
    	}
    
    	for (int i = 0; i < k; ++i)
    		output[i] = input[i];
    }
    
    int Partition(int *a, int low, int high){
    	int pivot = a[low];
    	while (low < high){
    		while (low < high && a[high] >= pivot) --high;
    		a[low] = a[high];
    		while (low < high && a[low] <= pivot) ++low;
    		a[high] = a[low];
    	}
    	a[low] = pivot;
    	return low;
    }
    
    // 基于multiset的实现
    void getLeastNumbers(const vector<int> &data, multiset<int, int> & leastNumbers, int k){
    	leastNumbers.clear();
    
    	if (k < 1 || data.size() < k)
    		return;
    
    	vector<int>::const iterator = data.begin();
    	for (; iter != data.end(); ++iter){
    		if (leastNumbers.size() < k)
    			leastNumbers.insert(*iter);
    		else{
    			mutiset<int, int>::iterator setiter = leastNumbers.begin();
    			if (*iter < *(leastNumbers.begin())){
    				leastNumbers.erase(setiter);
    				leastNumbers.insert(iter);
    			}
    		}
    	}
    }
    

    7.3 Bit-map

    • 使用位数组来表示某些元素是否存在,采用bit作为单位存储数据

    • 时间复杂度为O(n),以空间换时间,根据具体情况需要n位的串

    • 例1: 40亿个不重复的unsigned int的值,没排过序,再给一个数,如何判断这个数是否在40个亿数中?
      答:unsigned int 最多2^32个数,需要申请512M的内存,一个bit位代表一个unsigned int的值,读入40亿个数,设置对应bit位,读入数,查询相应的位

    • 例2:4,7,2,5,3排序
      答:申请一个8位byte位,读入第一个值4,则将byte第5位置1,然后依次置位,最后遍历bit区域,将该位是1的编号输出

  • 相关阅读:
    页面自动打印
    水晶报表安装问题
    c#中重写属性
    水晶报表无法找到数据源问题
    SQL Server2000安装提示数据源配置错误解决方案
    由C/C++中的结构体包含数组引申到C/C++堆栈
    自写缓存类
    C语言fread和fwrite的例子
    C语言生成DLL供C#调用
    C语言操作文件总结
  • 原文地址:https://www.cnblogs.com/zoe-mine/p/7593675.html
Copyright © 2011-2022 走看看