C++編寫算法(五)——查找問題之符號表與二分查找

一、符號表

定義:符號表是一種存儲鍵值對的數(shù)據(jù)結構,支持兩種操作:插入(put)着饥,即將一組新的兼職對存入表中探膊;查找(get),即根據(jù)給定的鍵得到相應的值奴潘。

創(chuàng)建符號表

無序符號表

符號表即是鍵值對的存儲結構,結合最近剛學習的類的定義,將符號表寫成類的形式喉誊,對用戶隱藏了鍵值對的數(shù)據(jù),留給用戶一下接口調(diào)用鍵值對纵顾。
首先是創(chuàng)建一個無序表伍茄,無序表的創(chuàng)建比較簡單。輸入什么鍵值對便存儲什么鍵值對施逾。

// 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;
}

有序符號表

上面這種符號表僅實現(xiàn)了<Key,Value>對的存儲敷矫。經(jīng)過咨詢做服務器的同學例获,這種存儲有時候需要使有序的,有序可以是鍵的有序曹仗,也可以是值的有序榨汤。但是<Key,Value>中強調(diào)的是通過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;
}

符號表的應用

符號表的其中一種應用是詞頻統(tǒng)計啼器。上面的符號表需要根據(jù)詞頻統(tǒng)計的需求進行一定的修改。
1俱萍、插入鍵值對不再需要鍵入進行插入端壳,而是從txt文本中按字符串插入。
2枪蘑、尋找鍵值對需要通過鍵進行搜尋损谦,并非通過鍵值對。
3岳颇、需要加入一個修改value值的函數(shù)照捡,對詞頻進行計數(shù)。
4话侧、需要在插入時判斷key是否已經(jīng)存在栗精。

//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

最后統(tǒng)計可以得到如下結果:


詞頻統(tǒng)計結果.png

不僅統(tǒng)計了詞頻,而且單詞是按照字母排序的瞻鹏。
當然悲立,C++有更加高級的<Key,Value>模板可供選擇,本人的這個程序只是對符號表理解后新博,結合類的思想薪夕,對其進行一個簡單的實現(xiàn)。
C++可以使用unordered_map來實現(xiàn)<Key,Value>模板赫悄!更高效快捷地創(chuàng)建符號表原献。

查找算法

無序鏈表的順序查找

無序鏈表的順序查找的思路非常簡單。通過遍歷搜索和比較埂淮,查找到對應的鍵值對嚼贡。
這種方法實現(xiàn)比較簡單,之前的代碼就是通過順序查找的方法對相應值進行搜尋的同诫。但無序鏈表的順序查找算法的復雜度比較高,不利于實際的搜索樟澜。因此误窖,我們需要尋找一些更加便捷的算法叮盘。

向一個空表中插入N個不同的鍵需要~N^2/2次比較

有序數(shù)組的二分查找

1、二分查找所使用的符號表的數(shù)據(jù)結構是一對平行的數(shù)組霹俺,一個數(shù)組存儲鍵柔吼,一個數(shù)組存儲值。這樣方便查找數(shù)據(jù)丙唧。
2愈魏、使用有序數(shù)組存儲鍵值對的好處上面已經(jīng)提到,雖然在插入時步驟比較麻煩想际,但是在查詢時能夠使用復雜度更低的算法培漏。
二分查找可以用兩種方法實現(xiàn):
--根據(jù)之前的學習,通常對數(shù)組一分為二胡本,再二分為四等等的方法讓我想到了歸并排序以及快速排序的處理牌柄。沒錯,這樣的處理能采用遞歸思想侧甫。
--但遞歸算法比較麻煩珊佣,遞歸算法對我們這些小白特別不友好。因此披粟,可以采用迭代的方法進行處理咒锻。

先來看一個遞歸的版本,只需要在頭文件處加入聲明二分查找函數(shù)的語句:

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

然后在定義中加入該二分查找函數(shù)即可:

// 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已經(jīng)失去了元素可插入位置的信息碰镜。所以兢卵,應該采用lo作為返回值。

這里我總結一下我理解的迭代和遞歸在算法中的實現(xiàn)绪颖。
遞歸即是通過函數(shù)重復有規(guī)律的步驟秽荤。
迭代即是通過循環(huán)改變某些變量,以重復相同的步驟柠横。
遞歸是為了減少繁雜的邏輯而想出的巧妙的解決方法窃款。一般我們說“天才程序員使用遞歸”。比如漢諾塔游戲牍氛,如果采用非遞歸方法實現(xiàn)的話晨继,太繁雜。而使用遞歸僅用幾行代碼便可實現(xiàn)搬俊。但是遞歸的性能比較差紊扬,因此不是所有的算法都用遞歸實現(xiàn)就可以蜒茄。
迭代是以邏輯來設計的解決方法。雖然說使用遞歸比較帥餐屎,比較牛檀葛。但是有時候迭代的性能要比遞歸高的多。
所以使用迭代還是遞歸腹缩,需要取決實際屿聋。

一般情況下二分查找都比順序查找快得多,它也是眾多實際應用程序的最佳選擇藏鹊。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末润讥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子伙判,更是在濱河造成了極大的恐慌象对,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宴抚,死亡現(xiàn)場離奇詭異勒魔,居然都是意外死亡,警方通過查閱死者的電腦和手機菇曲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門冠绢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人常潮,你說我怎么就攤上這事弟胀。” “怎么了喊式?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵孵户,是天一觀的道長。 經(jīng)常有香客問我岔留,道長夏哭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任献联,我火速辦了婚禮竖配,結果婚禮上,老公的妹妹穿的比我還像新娘里逆。我一直安慰自己进胯,他們只是感情好,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布原押。 她就那樣靜靜地躺著胁镐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上希停,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天烁巫,我揣著相機與錄音,去河邊找鬼宠能。 笑死,一個胖子當著我的面吹牛磁餐,可吹牛的內(nèi)容都是我干的违崇。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼诊霹,長吁一口氣:“原來是場噩夢啊……” “哼羞延!你這毒婦竟也來了?” 一聲冷哼從身側響起脾还,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤伴箩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鄙漏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嗤谚,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年怔蚌,在試婚紗的時候發(fā)現(xiàn)自己被綠了巩步。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡桦踊,死狀恐怖椅野,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情籍胯,我是刑警寧澤竟闪,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站杖狼,受9級特大地震影響炼蛤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜本刽,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一鲸湃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧子寓,春花似錦暗挑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鲜屏,卻和暖如春烹看,著一層夾襖步出監(jiān)牢的瞬間国拇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工惯殊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酱吝,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓土思,卻偏偏與公主長得像务热,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子己儒,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容