大二上學期的數(shù)據(jù)結構課設的一道題讓我們實現(xiàn)單詞接龍,如今已經(jīng)大三上了,之前學的東西拿出來整理一下,順便分享給有需要的人, 由于代碼比較長,打包放在github倉庫:https://github.com/JiannBai/words-solitaire。
站在用戶的角度思考問題,與客戶深入溝通,找到汨羅網(wǎng)站設計與汨羅網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設計與互聯(lián)網(wǎng)技術結合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:成都網(wǎng)站設計、成都網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、主機域名、網(wǎng)絡空間、企業(yè)郵箱。業(yè)務覆蓋汨羅地區(qū)。1、題目簡述:結合數(shù)據(jù)結構課程的學習內容,設計并完成一個單詞接龍游戲。游戲開始 前,根據(jù)預先設定的詞典,建立合理的搜索結構。游戲開始后,根據(jù)用戶輸入 單詞的終止字符,從搜索結構中選擇以該字符作為起始字符的單詞并提供給用 戶。游戲結束后,統(tǒng)計用戶參與的接龍次數(shù)和得分。
游戲開始前:能夠從文本文件中讀取單詞信息,并設計合理的結構保存單詞數(shù)據(jù)。在文本讀取過程中,能夠對重復的單詞進行有效甄別,確保系統(tǒng)中單詞的唯一性。
游戲過程中:支持用戶輸入單詞,自動判別單詞的結尾字符,并從現(xiàn)有單 詞中快速查找所有以該字符開始的單詞,顯示在屏幕上供用戶選擇。給出的參 考單詞不能與已有的單詞重復,用戶也可以輸入提示單詞范圍之外的其他單詞。
設計合理的結構,記錄用戶每次選擇接龍的單詞。如果系統(tǒng)找不到滿足條件的單詞,游戲自動結束。用戶也可以手動終止游戲。
游戲結束后:統(tǒng)計用戶參與接龍的單詞總數(shù),并根據(jù)游戲順序,在屏幕上依次顯示所有參與接龍的單詞。
2、設計思想 2.1總體思路2.1.1字典樹作為底層的存儲單詞的結構,具有以下功能:
插入單詞
查找單詞
查找所有公共前綴的單詞
刪除單詞
返回前綴尾字母結點
2.1.2設計一個words類利用字典樹的存儲功能對接龍功能進行封裝,如
得到兩個單詞的重復部分
利用該重復部分作為前綴查找所有符合接龍規(guī)則的單詞
對接龍單詞的得分根據(jù)不同規(guī)則排序
記錄已經(jīng)接龍的單詞并確保其不會重復出現(xiàn)
改變單詞庫、接龍規(guī)則、得分規(guī)則等
2.1.3.設計控制ui頁面的類(這里不作為關注對象)用來控制交互操作。
動態(tài)記錄得分
動態(tài)顯示接龍單詞
更換單詞庫
錯誤提示
特點:
根結點不包含任何字母;
其余結點僅包含一個字母;
優(yōu)點:
利用串的公共前綴,方便尋找接龍單詞;
搜索的時間復雜度低(O(n)),僅與單詞長度有關。
存儲結構不因單詞量的增加而發(fā)生明顯變化,其所消耗內存上限只與單詞長度有關。
缺點:
消耗存儲空間較大,當單詞數(shù)較少時,存在較多冗余空間的消耗
在本程序中每個節(jié)點對應一個結構體,該字典樹的UML類圖表示為:
整個程序操作流程如下:
3、運行結果 3.1菜單頁面3.2游戲頁面3.3接龍過程3.4更換詞庫3.5更換記分規(guī)則3.6更換排序規(guī)則4、重點問題 4.1 字典樹的構建:解決方案:先設計結點結構體,然后進行字典樹功能接口的設計(參數(shù)、返回類型),進而對各個功能進行詳細實現(xiàn)。
4.2 搜索所有前綴單詞等功能的實現(xiàn)解決方案:利用遞歸將所有單詞找到存儲在一個字符串中,單詞與單詞之間用'#'隔離。
4.3 接龍功能的抽象解決方案:在word類中,實現(xiàn)得到所有前綴單詞的函數(shù)、得到單詞重復部分的函數(shù)、計算得分的函數(shù)、排序函數(shù),然后對其進行組裝進而實現(xiàn)單詞接龍的基本函數(shù)接口。
5、算法分析 5.1、存儲結構字典樹能夠存儲所有單詞的前綴,易于實現(xiàn)單詞的前綴查找搜索,且當前綴長度不固定時,搜索的時間復雜度具有很大的優(yōu)勢。
字典樹對單個單詞的搜索、刪除、插入的時間復雜度為O(n),n為單詞長度,時間復雜度較低。
字典樹存在一定的冗余存儲空間消耗,其空間復雜度最差情況為O(26^n),算是一種空間換時間的存儲結構。
由于存在較大的詞庫(10萬單詞)并且不需要單詞的順序固定,在考慮到游戲的流暢性,使用快速排序這種不穩(wěn)定但是平均時間復雜度較低的算法(為O(nlog(n)))
6、代碼由于代碼比較長,打包放在github倉庫:https://github.com/JiannBai/words-solitaire,下面給出部分核心代碼。
6.1 字典樹.h
#ifndef TRIETREE_H
#define TRIETREE_H
#include#include#includestruct TrieNode
{
QChar ch;//該節(jié)點存儲的字符
TrieNode *children[27];//
bool is_word;//是否為一個單詞
qint8 childrenNums;
TrieNode():ch('#'),is_word(false),childrenNums(0){
for(int i=0;i<27;++i)
{
children[i]=nullptr;
}
}
};
class TrieTree : public QObject
{
Q_OBJECT
public:
explicit TrieTree(QObject *parent = nullptr);
explicit TrieTree(const QString &fileName,QObject *parent = nullptr);
bool insert(QChar *word);
bool find_string(const QString &s,bool isEliminate=true);
//找到相同前綴的單詞
std::shared_ptrfindWordsWithSuffix(const QString &prefix,int nums);
bool delet(const QString &s);
bool insert(const QString &s);
QString getPrefixWords(const QString &prefix);
//隨機得到一個單詞
QString get_a_random_word(int d_time=0);
private:
TrieNode *root;//根節(jié)點
int nums;//葉節(jié)點數(shù)量
QString words_list;//以'#'為分隔符,存儲公共前綴單詞
TrieNode* startWith(TrieNode *tempNode,const QString prefix);
void DFS(const TrieNode* tempRoot,QString prefix);
signals:
};
#endif // TRIETREE_H
.cpp
#include "trietree.h"
#include#include#include#include#includeTrieTree::TrieTree(QObject *parent) : QObject(parent)
{
root=new TrieNode();
}
TrieTree::TrieTree(const QString &fileName,QObject *parent) : QObject(parent),root(new TrieNode),nums(0),words_list("")
{
QString wordsPath=fileName;
//讀取單詞文件,相對路徑
QFile wordsFile(wordsPath);
//打開文件(只讀模式)
wordsFile.open(QFileDevice::ReadOnly);
//讀取文件內容,一行一行讀
QString wordsArray;
int charToInt;
TrieNode *tempNode=nullptr;
if(wordsFile.isOpen())
{
while(!wordsFile.atEnd())//讀取文件中的每一行
{
wordsArray=QString(wordsFile.readLine());
//this->insert(wordsArray);
tempNode=root;
for(QChar c:wordsArray)//遍歷每一個單詞構建字典樹
{
charToInt=c.toLatin1();//應該不會改變c的類型
if(charToInt>96 && charToInt<123)//小寫字母
{
//qchar無法直接轉為int 或者qint
if(tempNode->children[charToInt-97]==nullptr)
{
TrieNode *newTrieNode=new TrieNode;
newTrieNode->ch=c;
tempNode->children[charToInt-97]=newTrieNode;//將新節(jié)點連接在當前節(jié)點對應的指針上
++(tempNode->childrenNums);//該節(jié)點的孩子數(shù)+1
tempNode=newTrieNode;//孩子結點變?yōu)楫斍肮?jié)點
newTrieNode=nullptr;
}
else
{
tempNode=tempNode->children[charToInt-97];
}
}
else if(charToInt>64 && charToInt<91)
{
if(tempNode->children[charToInt-65]==nullptr)
{
TrieNode *newTrieNode=new TrieNode;
newTrieNode->ch=c;
tempNode->children[charToInt-65]=newTrieNode;//將新節(jié)點連接在當前節(jié)點對應的指針上
++(tempNode->childrenNums);//該節(jié)點的孩子數(shù)+1
tempNode=newTrieNode;//孩子結點變?yōu)楫斍肮?jié)點
newTrieNode=nullptr;
}
else
{
tempNode=tempNode->children[charToInt-65];
}
}
else if(charToInt==45)//連接符'-'
{
if(tempNode->children[26]==nullptr)
{
TrieNode *newTrieNode=new TrieNode;
newTrieNode->ch=c;
tempNode->children[26]=newTrieNode;//將新節(jié)點連接在當前節(jié)點對應的指針上
++(tempNode->childrenNums);//該節(jié)點的孩子數(shù)+1
tempNode=newTrieNode;//孩子結點變?yōu)楫斍肮?jié)點
newTrieNode=nullptr;
}
else
{
tempNode=tempNode->children[26];
}
}
}
tempNode->is_word=true;
++this->nums;
}
}
else
{
qDebug()<<"open fail!"<<'\n';
}
wordsFile.close();
}
//查找某個單詞是否在字典樹中,isEliminate表示是否提出該單詞,默認為真
bool TrieTree::find_string(const QString &s,bool isEliminate)
{
TrieNode *tempNode=root;
char ch;
for(QChar c:s)
{
ch=c.toLatin1();
//若當前節(jié)點為空,則該單詞一定沒有
if(tempNode==nullptr) return false;
//若不為空
else if(ch>='a' && ch<='z')
{
//若當前節(jié)點的下一節(jié)點不為空,但此節(jié)點該字符為'#'
if(tempNode->children[ch-'a']!=nullptr && tempNode->children[ch-'a']->ch=='#')
{
return false;
}
//將當前節(jié)點下移
tempNode=tempNode->children[ch-'a'];
}
else if(ch>='A' && ch<='Z')
{
if(tempNode->children[ch-'A']!=nullptr && tempNode->children[ch-'A']->ch=='#')
{
return false;
}
tempNode=tempNode->children[ch-'A'];
}
else if(ch=='-')
{
if(tempNode->children[26]!=nullptr && tempNode->children[26]->ch=='#')
{
return false;
}
tempNode=tempNode->children[26];
}
}
//判斷結尾節(jié)點是否為一個單詞的末尾
if(tempNode!=nullptr && tempNode->is_word==true)
{
if(isEliminate) tempNode->is_word=false;
return true;
}
return false;
}
//若找到,返回最后一個字母的節(jié)點
TrieNode* TrieTree::startWith(TrieNode *tempNode,const QString prefix)
{
//將指針移到前綴的最后一個字符上
char ch;
for(QChar c:prefix)
{
ch=c.toLatin1();
if(tempNode!=nullptr && ch>='a' && ch<='z')
{
tempNode=tempNode->children[ch-'a'];
}
else if(tempNode!=nullptr && ch>='A' && ch<='Z' )
{
tempNode=tempNode->children[ch-'A'];
}
else if(tempNode!=nullptr && ch=='-' )
{
tempNode=tempNode->children[26];
}
else
{
qDebug()<<"無該前綴,查詢失??!"<<'\n';
tempNode=nullptr;
return tempNode;
}
}
return tempNode;
}
//傳入一個節(jié)點,以該節(jié)點為根節(jié)點做深度遍歷,找到所有公共前綴的單詞,存入words_list
//賊難寫這里
void TrieTree::DFS(const TrieNode* tempRoot,QString prefix)
{
if(tempRoot==nullptr) return;
//每個單詞以‘#‘為分隔符
if(tempRoot->is_word)
{
this->words_list.push_back(prefix+'#');
}
//若為葉節(jié)點,則返回
if(tempRoot->childrenNums==0) return;
//從根節(jié)點往下深度優(yōu)先遍歷
for(int i=0;i<27;++i)
{
if(tempRoot->children[i]!=nullptr)
{
//深度優(yōu)先遍歷
DFS(tempRoot->children[i],prefix+tempRoot->children[i]->ch);
}
}
}
//返回該前綴的所有單詞,以字符串的形式,各單詞以'#'分隔
QString TrieTree::getPrefixWords(const QString &prefix)
{
//每次開始清空字符串單詞
this->words_list=NULL;
TrieNode *tempNode=root;
tempNode=startWith(tempNode,prefix);
if(tempNode==nullptr)
{
return words_list;
}
;//沒有該前綴開頭的單詞,返回空
DFS(tempNode,prefix);
return this->words_list;
}
//插入單詞
bool TrieTree::insert(const QString &s)
{
TrieNode *tempNode = root;
if(find_string(s,false))
{
qDebug()<<"該字符串已經(jīng)存在!"<<'\n';
return 0;
}
else
{
int charToInt;
for(QChar c:s)//遍歷每一個單詞構建字典樹
{
charToInt=c.toLatin1();//應該不會改變c的類型
if(charToInt>96 && charToInt<123)//小寫字母
{
//qchar無法直接轉為int 或者qint
if(tempNode->children[charToInt-97]==nullptr)
{
TrieNode *newTrieNode=new TrieNode;
newTrieNode->ch=c;
tempNode->children[charToInt-97]=newTrieNode;//將新節(jié)點連接在當前節(jié)點對應的指針上
++(tempNode->childrenNums);//該節(jié)點的孩子數(shù)+1
tempNode=newTrieNode;//孩子結點變?yōu)楫斍肮?jié)點
newTrieNode=nullptr;
}
else
{
tempNode=tempNode->children[charToInt-97];
}
}
else if(charToInt>64 && charToInt<91)
{
if(tempNode->children[charToInt-65]==nullptr)
{
TrieNode *newTrieNode=new TrieNode;
newTrieNode->ch=c;
tempNode->children[charToInt-65]=newTrieNode;//將新節(jié)點連接在當前節(jié)點對應的指針上
++(tempNode->childrenNums);//該節(jié)點的孩子數(shù)+1
tempNode=newTrieNode;//孩子結點變?yōu)楫斍肮?jié)點
newTrieNode=nullptr;
}
else
{
tempNode=tempNode->children[charToInt-65];
}
}
else if(charToInt==45)//連接符'-'
{
if(tempNode->children[26]==nullptr)
{
TrieNode *newTrieNode=new TrieNode;
newTrieNode->ch=c;
tempNode->children[26]=newTrieNode;//將新節(jié)點連接在當前節(jié)點對應的指針上
++(tempNode->childrenNums);//該節(jié)點的孩子數(shù)+1
tempNode=newTrieNode;//孩子結點變?yōu)楫斍肮?jié)點
newTrieNode=nullptr;
}
else
{
tempNode=tempNode->children[26];
}
}
}
tempNode->is_word=true;
++this->nums;
return 1;
}
}
//大小寫問題,存進去有的本該大寫,但是與小寫的為公共前綴
bool TrieTree::delet(const QString &s)
{
TrieNode *tempNode = root;
char ch;
for(QChar c:s)
{
ch=c.toLatin1();
if(tempNode!=nullptr && ch>='a' && ch<='z')
{
tempNode=tempNode->children[ch-'a'];
}
else if(tempNode!=nullptr && ch>='A' && ch<='Z' )
{
tempNode=tempNode->children[ch-'A'];
}
else if(tempNode!=nullptr && ch=='-' )
{
tempNode=tempNode->children[26];
}
else
{
qDebug()<<"無該單詞,刪除失??!"<<'\n';
return false;
}
}
//最后的結點若是單詞,則刪除
if(tempNode->is_word==true) tempNode->is_word=false;
else return false;
return true;
}
QString TrieTree::get_a_random_word(int d_time)
{
//隨機種子
srand(time(NULL)+d_time);
TrieNode *tempNode=root;
QString word;
while(!tempNode->is_word)
{
int random_number=rand()%26;;
if(tempNode->children[random_number] && tempNode->children[random_number]->ch!='#')
{
word.push_back(tempNode->children[random_number]->ch);
tempNode=tempNode->children[random_number];
}
}
return word;
}
6.2 控制詞語接龍核心邏輯的類.h
#ifndef WORD_H
#define WORD_H
#include#include#include#include"trietree.h"
struct wordsWithGoal
{
QString words="";
int goal=-1;
int next_words_num=999999;
bool is_depend_less_word=false;
bool operator>(const wordsWithGoal &other)
{
if(!is_depend_less_word)return this->goal>other.goal;
else return this->next_words_num>other.next_words_num;
}
bool operator<(const wordsWithGoal &other)
{
if(!is_depend_less_word)return this->goalnext_words_num=(const wordsWithGoal &other)
{
if(!is_depend_less_word)return this->goal>=other.goal;
else return this->next_words_num>=other.next_words_num;
}
bool operator<=(const wordsWithGoal &other)
{
if(!is_depend_less_word)return this->goal<=other.goal;
else return this->next_words_num<=other.next_words_num;
}
void operator=(const wordsWithGoal &other)
{
this->goal=other.goal;
this->words=other.words;
this->is_depend_less_word=other.is_depend_less_word;
this->next_words_num=other.next_words_num;
}
};
class word : public QObject
{
Q_OBJECT
public:
explicit word(QObject *parent = nullptr);
//設置單詞文本
void setWordsTxt(const QString &fileName);
//初始化游戲頁面,隨機初始化一個單詞,未實現(xiàn)
QString initial_word(int d_time=0);
//查找某一單詞是否存在于詞庫中,若存在則返回該單詞,否則返回空字符串;
QString find(const QString &word,bool is_useless_words=true);
//字符串后幾位的匹配,按接龍匹配程度返回匹配到的前m個合法單詞
std::shared_ptrwords_matching(QString prefix,int num=20);
//返回該單詞和接龍單詞的單詞重疊部分
QString get_prefix(const QString &pred_word,const QString & connect_word);
//設置規(guī)則
void setRule(const bool &is_overlap_goal);
//計算得分
int get_goals(QString prifix,QString word);
//得到該單詞的后續(xù)單詞數(shù)
int get_next_words_num(QString word);
void setOrderRule(const bool &is_depend_less_words);
//存儲單詞的數(shù)據(jù)結構
TrieTree *wordsTree=nullptr;
//接過龍的單詞
TrieTree *UselesswordsTree;
private:
//是否使用重疊得分規(guī)則
bool is_overlap_goal=false;
//排序會澤
bool is_depend_less_words=false;
//快速排序
void quick_sort(wordsWithGoal *&connectWords,int begin,int all_words_number);
signals:
//查找某個單詞用于接龍的信號
void find_input_word();
//游戲開始信號
void start_game();
};
#endif // WORD_H
.cpp
#include "word.h"
#include"trietree.h"
#include#includeword::word(QObject *parent) : QObject(parent),UselesswordsTree(new TrieTree),is_overlap_goal(false)
{
}
//用于為存儲結構放入單詞
void word::setWordsTxt(const QString &fileName)
{
//若該樹為空
if(this->wordsTree==nullptr) this->wordsTree=new TrieTree(fileName);
//若該樹不為空
else
{
delete this->wordsTree;
wordsTree=new TrieTree(fileName);
}
}
//初始化游戲頁面,隨機初始化一個單詞
QString word::initial_word(int d_time)
{
return this->wordsTree->get_a_random_word(d_time);
}
//設置規(guī)則
void word::setRule(const bool &is_overlap_goal)
{
if(is_overlap_goal) this->is_overlap_goal=true;
else this->is_overlap_goal=false;
}
//詞庫中是否有該單詞,有則返回,沒有返回空字符串
QString word::find(const QString &word,bool is_useless_words)
{
//查詢剔除后的單詞樹
if(is_useless_words)
{
if(this->UselesswordsTree->find_string(word,false))
{
return word;
}
else return "";
}
else
{
if(this->wordsTree->find_string(word))
{
return word;
}
else return "";
}
}
//得到兩個接龍單詞大重疊字符串(暴力搜索,可改用KMP)
QString word::get_prefix(const QString &pre_word,const QString & connect_word)
{
int pre_length=pre_word.length();
int con_lenght=connect_word.length();
int min_length=pre_lengthmax_str_num) max_str_num=temp_str_num;
}
return connect_word.mid(0,max_str_num);
}
//返回前num個接龍單詞和對應的分數(shù)
std::shared_ptrword::words_matching(QString prefix_words,int num)
{
QVectorallPrifixWords;
//所有符合規(guī)則的單詞
QString allConnectWords;
if(!is_overlap_goal)allConnectWords=this->wordsTree->getPrefixWords(prefix_words[prefix_words.length()-1]);
else
{
int pre_word_length=prefix_words.length();
for(int i=1;iwordsTree->getPrefixWords(prefix_words.mid(i,pre_word_length-1));
}
}
//得到所有符合條件的單詞
QString temp_string;
for(QChar ch:allConnectWords)
{
if(ch=='#')
{
allPrifixWords.push_back(temp_string);
temp_string="";
continue;
}
else temp_string.push_back(ch);
}
//將所有公共前綴單詞結構體存入自己的數(shù)組中
int all_words_number=allPrifixWords.length();//全部符合規(guī)則的單詞
wordsWithGoal *connectWords= new wordsWithGoal[all_words_number];
for(int i=0;iget_goals(prefix_words,allPrifixWords[i]);
connectWords[i].next_words_num=this->get_next_words_num(allPrifixWords[i]);
connectWords[i].is_depend_less_word=this->is_depend_less_words;
}
//排序
quick_sort(connectWords,0,all_words_number-1);
//存入要返回的數(shù)組中(智能指針)
std::shared_ptrresule_words(new wordsWithGoal[num]);
if(!is_depend_less_words)//按得分由高到低
{
num=numall_words_number-num;--i)
{
resule_words[all_words_number-i]=connectWords[i-1];
}
}
else
{
for(int i=all_words_number;i>0;--i)
{
resule_words[all_words_number-i]=connectWords[i-1];
}
}
}
delete [] connectWords;
connectWords=nullptr;
return resule_words;
}
//計算一個接龍單詞的得分
int word::get_goals(QString prefix_word,QString word)
{
//若是規(guī)則一
if(!this->is_overlap_goal)
{
if(prefix_word[prefix_word.length()-1]==word[0])return word.length();
else return 0;
}
else
{
return this->get_prefix(prefix_word,word).length()*word.length();
}
}
//快速排序 不穩(wěn)定
void word::quick_sort(wordsWithGoal *&a,int l,int r)
{
if (l >= r) return;
int t1 = l, t2 = r;
wordsWithGoal key = a[(l + r) / 2];
do {
while (a[t1] >key) t1++;
while (a[t2]< key) t2--;
if (t1<= t2)
{
wordsWithGoal temp=a[t1];
a[t1]=a[t2];
a[t2]=temp;
t1++,t2--;
}
} while (t1<= t2);
quick_sort(a,l, t2);
quick_sort(a,t1, r);
}
int word::get_next_words_num(QString word)
{
int num=0;
if(!this->is_overlap_goal)
{
QString s=this->wordsTree->getPrefixWords(word[word.length()-1]);
for(QChar c:s)
{
if(c=='#')++num;
}
}
else
{
int pre_word_length=word.length();
QString s;
for(int i=1;iwordsTree->getPrefixWords(word.mid(i,pre_word_length-i));
}
for(QChar c:s)
{
if(c=='#')++num;
}
}
return num;
}
void word::setOrderRule(const bool &is_depend_less_words)
{
this->is_depend_less_words=is_depend_less_words;
}
你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧