C++编写算法(五)——查找问题之符号表与二分查找

一、符号表

定义:符号表是一种存储键值对的数据结构,支持两种操作:插入(put),即将一组新的兼职对存入表中;查找(get),即根据给定的键得到相应的值。

创建符号表

无序符号表

符号表即是键值对的存储结构,结合最近刚学习的类的定义,将符号表写成类的形式,对用户隐藏了键值对的数据,留给用户一下接口调用键值对。
首先是创建一个无序表,无序表的创建比较简单。输入什么键值对便存储什么键值对。

// JunzHeader.h
#ifndef JunzHeader_H_
#define JunzHeader_H_
struct DATA
{
    char key;
    int value;
};

typedef DATA DataType;

class SLType
{
private:
    enum { MAX = 100 }; // define the max length of table
    DataType data[MAX]; 
    int TbLength;       // length at present
public:
    SLType();                               // create an empty table
    int CalculateLength(); // calculate the length of Table
    bool insertNode();           // insert a node
    bool deleteNode(DataType kv); // delete a node
    int searchNode(DataType kv);  // search a node
    void show() const;        // display table
    
};
#endif // !JunzHeader_H_
//JunzHeader.cpp
#include<iostream>
#include"JunzHeader.h"

// create an empty table
SLType::SLType()
{
    for (int i = 0; i < MAX; i++)
        data[i] = { '0', 0 };
    TbLength = 0;
}

int SLType::CalculateLength()
{
    return TbLength;
}

bool SLType::insertNode()
{
    using std::cout;
    using std::cin;
    if (TbLength >= MAX - 1)
        return false;
    else
    {
        cout << "Please input the key: ";
        cin >> data[TbLength].key;
        cout << "Please input the value: ";
        cin >> data[TbLength].value;
        TbLength = TbLength + 1;
        return true;
    }
}

int SLType::searchNode(DataType kv)
{
    int temp = -2;
    if (TbLength == 0)
    {
        return -1;
    }
    else
    {
        for (int i = 0; i < TbLength; i++)
        {
            if (kv.key == data[i].key)
                temp = i;
            else
                continue;
        }
        return temp;
    }
}


bool SLType::deleteNode(DataType kv)
{
    if (TbLength == 0)
        return false;
    else
    {
        int temp = searchNode(kv);
        if (temp == -2)
            return false;
        else
        {
            for (int j = temp + 1; j < TbLength; j++)
            {
                data[j - 1].key = data[j].key;
                data[j - 1].value = data[j].value;
            }
            TbLength = TbLength - 1;
            return true;
        }
    }
}

void SLType::show() const
{
    using std::cout;
    using std::endl;
    for (int i = 0; i < TbLength; i++)
    {
        cout << data[i].key << " " << data[i].value << endl;
    }
}
//Main.cpp
#include <iostream>
#include "JunzHeader.h"
using namespace std;
const int NUM = 5;
int main()
{
    SLType ST;                     // create an empty table
    bool flag;
    for (int i = 0; i < NUM; i++)
    {
        flag = ST.insertNode();
        if (flag == false)
        {
            cout << "Input Error in " << i + 1 << "step!" << endl;
            break;
        }
    }
    cout << "A:10 is in " << ST.searchNode({ 'A',10 }) + 1 << endl;
    ST.deleteNode({ 'A',10 });
    cout << "A:10 is delete!" << endl;
    ST.show();
        
    system("pause");
    return 0;
}

有序符号表

上面这种符号表仅实现了<Key,Value>对的存储。经过咨询做服务器的同学,这种存储有时候需要使有序的,有序可以是键的有序,也可以是值的有序。但是<Key,Value>中强调的是通过Key寻求对应的Value值,因此,采用键有序是比较合理的。顺序符号表的好处是易查找。但是它需要在插入和删除表元素中付出较无序表更加高昂的代价。

//JunzHeader.h
#ifndef JunzHeader_H_
#define JunzHeader_H_
struct DATA
{
    char key;
    int value;
};

typedef DATA DataType;

class SLType
{
private:
    enum { MAX = 100 }; // define the max length of table
    DataType data[MAX]; 
    int TbLength;       // length at present
public:
    SLType();                               // create an empty table
    int CalculateLength(); // calculate the length of Table
    bool insertNode();           // insert a node
    bool deleteNode(DataType kv); // delete a node
    int searchNode(DataType kv);  // search a node
    void show() const;        // display table
    
};
#endif // !JunzHeader_H_
//JunzFuncDef.cpp
#include<iostream>
#include"JunzHeader.h"

// create an empty table
SLType::SLType()
{
    for (int i = 0; i < MAX; i++)
        data[i] = { '0', 0 };
    TbLength = 0;
}

int SLType::CalculateLength()
{
    return TbLength;
}

bool SLType::insertNode()
{
    using std::cout;
    using std::cin;
    int Lc = TbLength;
    DataType temp;
    if (TbLength >= MAX - 1)
        return false;
    else
    {
        cout << "Please input the key: ";
        cin >> data[TbLength].key;
        cout << "Please input the value: ";
        cin >> data[TbLength].value;

        temp = data[TbLength];

        for (int i = TbLength; i >= 0; i--)
        {
            if (data[TbLength].key < data[i].key)
                Lc = i;
            else
                continue;
        }
        if (Lc != TbLength)
        {
            for (int j = TbLength; j > Lc; j--)
            {
                data[j] = data[j - 1];
            }
            data[Lc] = temp;
        }

        TbLength = TbLength + 1;
        return true;
    }
}

int SLType::searchNode(DataType kv)
{
    int temp = -2;
    if (TbLength == 0)
    {
        return -1;
    }
    else
    {
        for (int i = 0; i < TbLength; i++)
        {
            if (kv.key == data[i].key)
                temp = i;
            else
                continue;
        }
        return temp;
    }
}


bool SLType::deleteNode(DataType kv)
{
    if (TbLength == 0)
        return false;
    else
    {
        int temp = searchNode(kv);
        if (temp == -2)
            return false;
        else
        {
            for (int j = temp + 1; j < TbLength; j++)
            {
                data[j - 1].key = data[j].key;
                data[j - 1].value = data[j].value;
            }
            TbLength = TbLength - 1;
            return true;
        }
    }
}

void SLType::show() const
{
    using std::cout;
    using std::endl;
    for (int i = 0; i < TbLength; i++)
    {
        cout << data[i].key << " " << data[i].value << endl;
    }
}
//Main.cpp
#include <iostream>
#include <fstream>
#include <string>
#include "JunzHeader.h"

using namespace std;
const int NUM = 5;
int main()
{
    SLType ST;                     // create an empty table
    bool flag;
    for (int i = 0; i < NUM; i++)
    {
        flag = ST.insertNode();
        if (flag == false)
        {
            cout << "Input Error in " << i + 1 << "step!" << endl;
            break;
        }
    }
    ST.show();
    cout << "A:10 is in " << ST.searchNode({ 'A',10 }) + 1 << endl;
    ST.deleteNode({ 'A',10 });
    cout << "A:10 is delete!" << endl;
    ST.show();
        
    system("pause");
    return 0;
}

符号表的应用

符号表的其中一种应用是词频统计。上面的符号表需要根据词频统计的需求进行一定的修改。
1、插入键值对不再需要键入进行插入,而是从txt文本中按字符串插入。
2、寻找键值对需要通过键进行搜寻,并非通过键值对。
3、需要加入一个修改value值的函数,对词频进行计数。
4、需要在插入时判断key是否已经存在。

//JunzHeader.h
#include <iostream>

#ifndef JunzHeader_H_
#define JunzHeader_H_
struct DATA
{
    std::string key;
    int value;
};

typedef DATA DataType;

class SLType
{
private:
    enum { MAX = 100 }; // define the max length of table
    DataType data[MAX]; 
    int TbLength;       // length at present
public:
    SLType();                               // create an empty table
    int CalculateLength(); // calculate the length of Table
    bool insertNode(std::string & str);           // insert a node
    int searchNode(std::string & str);  // search a node
    bool ModifyVal(std::string & str);  // modify the value in a certain key
    void show() const;        // display table
    
};
#endif // !JunzHeader_H_
//JunzFuncDef.cpp
#include<iostream>
#include<string>
#include"JunzHeader.h"

// create an empty table
SLType::SLType()
{
    for (int i = 0; i < MAX; i++)
        data[i] = { "0000", 0 };
    TbLength = 0;
}

int SLType::CalculateLength()
{
    return TbLength;
}

bool SLType::insertNode(std::string & s)
{
    using std::cout;
    using std::cin;
    int Lc = TbLength;
    DataType temp;
    if (TbLength >= MAX - 1)
        return false;
    else
    {
        data[TbLength].key = s;
        data[TbLength].value += 1;
        temp = data[TbLength];

        for (int i = TbLength; i >= 0; i--)
        {
            if (data[TbLength].key < data[i].key)
                Lc = i;
            else
                continue;
        }
        if (Lc != TbLength)
        {
            for (int j = TbLength; j > Lc; j--)
            {
                data[j] = data[j - 1];
            }
            data[Lc] = temp;
        }

        TbLength = TbLength + 1;
        return true;
    }
}

int SLType::searchNode(std::string & str)
{
    int temp = -2;
    if (TbLength == 0)
    {
        return -1;
    }
    else
    {
        for (int i = 0; i < TbLength; i++)
        {
            if (str == data[i].key)
                temp = i;
            else
                continue;
        }
        return temp;
    }
}


void SLType::show() const
{
    using std::cout;
    using std::endl;
    for (int i = 0; i < TbLength; i++)
    {
        cout << data[i].key << " " << data[i].value << endl;
    }
}

bool SLType::ModifyVal(std::string & str)
{
    int index_tmp = searchNode(str);
    if (index_tmp < 0)
        return false;
    else
    {
        data[index_tmp].value += 1;
        return true;
    }
}
//Main.cpp
#include <iostream>
#include <fstream>
#include <string>
#include "JunzHeader.h"

using namespace std;
const int NUM = 5;
int main()
{
    SLType ST;
    bool flag1,flag2;
    ifstream ifs("tinyTale.txt");
    string temp;
    while (ifs >> temp)
    {
        if (ST.searchNode(temp) < 0)
        {
            flag1 = ST.insertNode(temp);
            if (flag1 == false)
            {
                cout << "Input Error!!!" << endl;
                break;
            }
        }   
        else
            flag2 = ST.ModifyVal(temp);
    }
    ifs.close();
    ST.show();
    system("pause");
    return 0;
}

所采用的tinyTale.txt文件采用了狄更斯的《双城记》中的一段话:

it was the best of time it was the worst of times
it was the age of wisdom it was the age of foolishness
it was the epoch of belief it was the epoch of incredulity
it was the season of light it was the season of darkness
it was the spring of hope it was the winter of despair

最后统计可以得到如下结果:


词频统计结果.png

不仅统计了词频,而且单词是按照字母排序的。
当然,C++有更加高级的<Key,Value>模板可供选择,本人的这个程序只是对符号表理解后,结合类的思想,对其进行一个简单的实现。
C++可以使用unordered_map来实现<Key,Value>模板!更高效快捷地创建符号表。

查找算法

无序链表的顺序查找

无序链表的顺序查找的思路非常简单。通过遍历搜索和比较,查找到对应的键值对。
这种方法实现比较简单,之前的代码就是通过顺序查找的方法对相应值进行搜寻的。但无序链表的顺序查找算法的复杂度比较高,不利于实际的搜索。因此,我们需要寻找一些更加便捷的算法。

向一个空表中插入N个不同的键需要~N^2/2次比较

有序数组的二分查找

1、二分查找所使用的符号表的数据结构是一对平行的数组,一个数组存储键,一个数组存储值。这样方便查找数据。
2、使用有序数组存储键值对的好处上面已经提到,虽然在插入时步骤比较麻烦,但是在查询时能够使用复杂度更低的算法。
二分查找可以用两种方法实现:
--根据之前的学习,通常对数组一分为二,再二分为四等等的方法让我想到了归并排序以及快速排序的处理。没错,这样的处理能采用递归思想。
--但递归算法比较麻烦,递归算法对我们这些小白特别不友好。因此,可以采用迭代的方法进行处理。

先来看一个递归的版本,只需要在头文件处加入声明二分查找函数的语句:

// addition in JunzHeader.h
int Binarysearch(char k, int lo, int hi);

然后在定义中加入该二分查找函数即可:

// JunzFuncDef.cpp
#include<iostream>
#include<string>
#include"JunzHeader.h"

// create an empty table
SLType::SLType()
{
    for (int i = 0; i < MAX; i++)
    {
        Key[i] = '0';
        Value[i] = 0;
    }
    TbLength = 0;
}

int SLType::CalculateLength()
{
    return TbLength;
}

bool SLType::insertNode()
{
    using std::cout;
    using std::cin;
    int Lc = TbLength;
    char temp1;
    int temp2;
    if (TbLength >= MAX - 1)
        return false;
    else
    {
        cout << "Please Input the Key: ";
        cin >> Key[TbLength];
        cout << "Please Input the value: ";
        (cin >> Value[TbLength]).get();
        temp1 = Key[TbLength];
        temp2 = Value[TbLength];

        int index = Binarysearch(Key[TbLength], 0, TbLength);
        Lc = index;
        if (Lc != TbLength)
        {
            for (int j = TbLength; j > Lc; j--)
            {
                Key[j] = Key[j - 1];
                Value[j] = Value[j - 1];
            }
            Key[Lc] = temp1;
            Value[Lc] = temp2;
        }
        TbLength = TbLength + 1;
        return true;
    }
}

int SLType::Binarysearch(char k, int lo, int hi)
{ 
    if (hi <= lo)
        return lo;
    int mid = (hi - lo) / 2 + lo;
    if (k < Key[mid])
        return Binarysearch(k, lo, mid - 1);
    else if (k > Key[mid])
        return Binarysearch(k, mid + 1, hi);
    else
        return mid;
}

std::ostream & operator<<(std::ostream & os, const SLType & s)
{
    for (int i = 0; i < s.TbLength; i++)
    {
        os << s.Key[i] << " " << s.Value[i] << "\n";
    }
    return os;
}

递归固然能帮助理解思路,但是迭代更加高效。所以二分查找也有迭代版本:

//
int SLType::Binarysearch(char k, int lo, int hi)
{ 
    int mid = 0;
    while (true)
    {
        if (hi <= lo)
            break;
        mid = lo + (hi - lo) / 2;
        if (Key[mid] < k)
        {
            lo = mid + 1;
            continue;
        }
        else if (Key[mid] > k)
        {
            hi = mid - 1;
            continue;
        }
        else
            return mid;
    }
    return lo;
        
}

这里在编程的时候,return lo处我一开始编写的是return mid。这样是不对的,因为mid有可能会因为lo和hi取到前半段而变小。此时,mid已经失去了元素可插入位置的信息。所以,应该采用lo作为返回值。

这里我总结一下我理解的迭代和递归在算法中的实现。
递归即是通过函数重复有规律的步骤。
迭代即是通过循环改变某些变量,以重复相同的步骤。
递归是为了减少繁杂的逻辑而想出的巧妙的解决方法。一般我们说“天才程序员使用递归”。比如汉诺塔游戏,如果采用非递归方法实现的话,太繁杂。而使用递归仅用几行代码便可实现。但是递归的性能比较差,因此不是所有的算法都用递归实现就可以。
迭代是以逻辑来设计的解决方法。虽然说使用递归比较帅,比较牛。但是有时候迭代的性能要比递归高的多。
所以使用迭代还是递归,需要取决实际。

一般情况下二分查找都比顺序查找快得多,它也是众多实际应用程序的最佳选择。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容