0.PTA得分截圖

1.本周學習總結
1.1總結查找內容
查找算法的評價指標——ASL

ASL,關鍵字平均比較的次數,也稱平均搜索長度.
ASL(成功):指找到集合T中任一記錄平均需要的關鍵字比較次數
順序查找:

int SeqSearch(int *a,int n,int k)
{
int i=0;
while(i<n&&a[i]!=k)
{
i++;
}
if(i>=n) //沒找到,返回-1
{
return -1;
}
else
{
return i;
}
}
- 時間復雜度:O(n)
- 查找成功時的平均比較次數約為表長一半
- 查找不成功時的平均查找長度為:n
二分查找
二分查找也叫折半查找,要求線性表中的元素必須從小到大排列或者從大到小排列

int BinSearch(int *a,int n,int k)
{
int low=0,high=n-1,mid;
while(low<=high)
{
mid=(mid+high)/2;
if(a[mid]==k)
{
return mid+1;
}
if(k<a[mid])
{
high=mid-1;
}
else
{
low=mid+1;
}
}
return -1; //沒找到
}
//遞歸代碼
int BinSearch(int *a,int low,int high,int k)
{
int mid;
if(low<high)
{
mid=(low+high)/2;
if(a[mid]==k)
return mid+1;
if(a[mid]>k)
BinSearch(a, low,mid-1, k) ;
else
BinSearch(a, low,mid+1, k) ;
}
else
return -1;
}
//時間復雜度:T(n)=T(n/2)+1
- 時間復雜度:其實這樣的過程,while循環查找的方法就很像建立了一個二叉搜索樹,最后搜索的是一條路徑,所以時間復雜度是O(logn)
- ASL=log(n+1)-1
計算二分查找,二叉排序樹的ASL
給出如下例子

ASL(成功):(各個關鍵字比較的次數/有效節點)
1 * 1+2 * 2+3 *4 +4 * 3)/10=2.9
ASL(不成功):(各個空節點比較的次數/(總節點數+所有空節點)),不成功,說明肯定都找到葉子節點里去了,因此總節點數就是有效節點+空節點
(3 * 5+4 * 6)/11=3.54
二叉搜索(排序)樹
特點:
若左子樹不空,則右子樹所有節點的值均小於根節點的值
若右子樹不空,則右子樹所有節點的值均大於根節點的值
左右子樹也都是二叉排序樹
ps:
二叉排序中,沒有相同關鍵字的節點
中序遍歷二叉搜索樹,會發現得到的序列是遞增的
二叉排序樹結構體定義
typedef struct node
{
int key;
InfoType data;//其它數據域
struct node *lchild,*rchild;//左右孩子指針
}BSTNode,*BSTree;
建二叉排序樹
int InsertBST(BSTree &p,int k)
{
if(p==NULL)//插入永遠都在葉子節點插入
{
p=new BSTNode;
p->key=k;
p->lchild=p->rchild=NULL;
return 1;
}
else if(k==p->key)
{
return 0;
}
else if(k<p->key)
{
return InsertBST(p->lchild,k);
}
else
{
return InsertBST(p->rchild,k);
}
}
查找關鍵字
BSTNode *SearchBST(BSTNode *bt,int k)
{
if(bt==NULL||bt->key==k)
{
return bt;
}
if(k<bt->key)
{
return SearchBST(bt->lchild, k);
}
else
{
return SearchBST(bt->rchild, k);
}
}
//非遞歸查找
BSTNode *SearchBST1(BSTNode *bt,int k)
{
while(bt!=NULL)
{
if(k--bt->key)
{
return bt;
}
else if(k<bt->key)
{
bt=bt->lchild;
}
else
bt=bt->rchild;
}
return NULL;
}
查找最大/最小關鍵字節點
int maxnode(BSTNode *p)
{
while (p->rchild!=NULL)//最大值一定在最右不為空的葉子節點
p=p->rchild;
return(p->data);
}
int minnode(BSTNode *p)
{
while (p->lchild!=NULL)//最小值一定在最左不為空的葉子節點
p=p->lchild;
return(p->data);
}
二叉排序樹刪除葉子節點

直接將雙親節點指向葉子節點的指針域指向空,接着刪除葉子節點
二叉排序樹刪除的節點只有左子樹或右子樹

對於刪除只有左孩子或右孩子的節點,只要讓雙親節點的指針域指向它的左孩子或右孩子,對於上面這個圖,對於30一個節點,刪除它的右孩子40,那么無論是40的左孩子還是右孩子,都會比30大,因為我們要保持二叉搜索樹的特點,所以直接把30這個節點的指針域指向40這個節點的左孩子或右孩子即可
二叉排序樹刪除的節點既有左子樹和右子樹
這是刪除節點中最麻煩的一個,但是我們只要始終記住二叉樹左右孩子,和根節點的關系即可
刪除節點,有兩種辦法
1.以前驅代替之,再刪除該節點,前驅就是左子樹中最大的節點
2.以后繼代替之,再刪除該節點,后繼就是右子樹中最小的節點
為什么這么選,就是因為要保持二叉搜索樹的特性,左子樹<根節點<右子樹

找到50,選擇左子樹最大節點進行代替,用圖畫出來其實可以很明了,直接看他左子樹30,40,35,最大的就是40,直接代替50,接着我們就要安排接下來35這個節點,其實一開始學的,我很懵,因為對於剩下節點我不知道怎么安排,后來,學着學着,就有感覺來,35不是比30大嘛,就直接放它的右子樹好了

對於這個圖,我畫的有點不好,因為對於從50到80這個指針域並沒有斷掉,因為刪除節點,是把50這個節點的數據域改了一下,我們找到50,然后找到右子樹的最小節點,80,75,90,85,最小,就是75,把50的值改為75,接着,由於75下面也沒啥節點了,就不處理了,如果有的話,記住特性安排,就好了。
int DeleteBST(BSTree &bt,KeyType k) //查找要刪除的節點
{ if (bt==NULL) return 0; //空樹刪除失敗
else if(k<bt->key)
{
return DeleteBST(bt->lchild,k);
//遞歸在左子樹中刪除為k的節點
}
else if(k>bt->key)
{
return DeleteBST(bt->rchild,k);
//遞歸在右子樹中刪除為k的節點
}
else
{ Delete(bt); //刪除*bt節點
return 1;
}
}
}
void Delete(BSTreee &p) //從二叉排序樹中刪除*p節點
{ BSTNode *q;
if (p->rchild==NULL) //*p節點沒有右子樹的情況
{
q=p;
p=p->lchild;
delete q;
}
else if (p->lchild==NULL) //*p節點沒有左子樹
{
q=p;
p=p->rchild;
delete q;
}
else Delete1(p,p->lchild);
//*p節點既有左子樹又有右子樹的情況
//找到左子樹最大的節點,一定是左孩子的最右孩子節點
}
void Delete1(BSTNode *p,BSTNode *&r) //被刪節點:p,p的左子樹節點:r
{ BSTNode *q;
if (r->rchild!=NULL)//遞歸找最右下節點
Delete1(p,r->rchild)
else //找到了最右下節點*r
{
//將*r的關鍵字值賦給*p
p->key=r->key;
q=r;
r=r->lchild;
delete q;
}
}
平衡二叉樹(AVL樹)
引入原因:為了解決二叉搜索樹極端的情況,如單支樹,那時,查找的ASL,就好順序查找一樣
特點:
1.左右子樹是平衡二叉樹
2.所有節點的左右子樹深度(高度)之差的絕對值<=1,在平衡二叉樹里,用平衡因子來表示左右子樹的高度之差,從而控制樹的平衡
由於有平衡因子的參與,所以不會存在最壞,最極端的情況,所以時間復雜度是O(logn)
AVL四種調節
對於AVL四種調節,我們要做的,也就只有一點,就是保證它是二叉搜索樹。
LL平衡旋轉

對於一棵樹中,發現有兩個失衡點,3和4,我們要從底部往上調節,第一個失衡點3,然后它的旋轉點就是2,讓2進行旋轉,往往我們從下面開始,第二個失衡點也會平衡,不在失衡
RR平衡調節

這個調整和LL調整一樣,我們要做的是找到失衡點和旋轉點,怎么旋轉,也是要看二叉搜索樹的特點,左子樹<根節點<右子樹
RL調整

插入9之后,發現8的平衡因子變為-2,所以失衡點是-2,開始尋找旋轉點,9是在8的右子樹的左孩子是,所以是RL調整,旋轉點是10,10代替8的位置,8往左下旋轉,那么本來是10的孩子9,就要按照AVL樹的特點,被安排為12的左孩子。
LR調整也是和上面一樣的。
節點個數n最少的平衡二叉樹

-
高度為h的平衡二叉樹,節點個數為N(h)=N(h-1)+N(h-2)+1
-
平衡二叉樹高度h和n的關系:h=log(n+1)
-
平衡二叉樹上最好 最壞進行查找關鍵字的比較次數不會超過平衡二叉樹的深度O(logn)
B(-)樹,B+樹
引用原因:BST,AVL都只適合小數據量,每個節點放一個關鍵字,當數據量大時,樹的高度也會很大
B樹和B+樹:一個節點可以放多個關鍵字,降低樹的高度,適合大數據量查找,如數據庫中的數據
B-樹,又叫做多路平衡查找樹
一棵m階B-樹
特點:
1.每個節點最多m個孩子,意味着關鍵字最多有m-1個
2.除了根節點至少有兩個孩子節點,其它節點都至少有m/2(上界)個孩子,說明節點最少有m/2(上界))-1個關鍵字。
3.B-樹是所有節點的平衡因子均等於0的多路查找樹,葉子節點必須在同一層
B-樹的結構體定義
#define MAXN 10
typedef int KeyTypw;
typedef struct node
{
int keynum;//節點當前擁有關鍵字的個數
KeyType key[MAXM];//存放關鍵字
struct node *parent;//雙親節點指針
struct node *ptr[MAXM];//孩子節點指針數組
}BTNode;
B-樹的插入
- 向B-樹中插入關鍵字,可能引起節點的分裂,最終可能導致整個B-樹的高度增一
- 插入的節點一定是葉子節點層
插入關鍵字后,節點關鍵字個數<m-1(m表示階數)

如果節點關鍵字個數<m-1,直接插入葉子節點即可,要注意,在節點里,插入的關鍵字要按順序排列
插入關鍵字后,節點關鍵字個數=m,此時需要進行分裂

由於葉子節點插入一個新的關鍵字,導致節點過飽和,所以我們要把多出來的一個節點給父親節點,而多出來的節點是按照序列排序,最中間的節點,不是最大的,也不是最小的,給了父親節點之后,發現父親節點可以表示的范圍區間多了一個,那接下來,就是按照區間把剩下的關鍵字進行排序即可。
B-樹的刪除(m表示階數)
- 在B-樹中刪除關鍵字,可能引起節點的合並,最終可能導致整個B-樹的高度減一
未刪除時節點中關鍵字個數>m/2(上界)-1,直接刪除
刪除葉子節點

刪除非葉子節點

節點中關鍵字個數=m/2(上界)-1
刪除葉子節點:
1.如果兄弟富余(節點個數>min),向兄弟借
節點方式是把兄弟節點的最小值或最大值(看情況)給父親節點,把父親節點的最大值或最小值給自己,這樣以后,才能繼續保持b樹的特點

2.如果兄弟節點的關鍵字數也等於min,沒有富余,那么只好進行合並

刪除非葉子節點
如果孩子富余,向孩子節點借,從中選擇最大或最小的值,要看情況選擇

B樹的應用
B樹常被用於對檢索時間要求苛刻的場合
1.B-樹索引是數據庫中存取和查找文件的方法
2.硬盤中的節點也是B-樹結構,B-樹利用多個分支(稱為子樹)的節點,減少獲取記錄時所經歷的節點數,從而達到節省存取時間的目的
B+樹
B+樹是大型索引文件的標准組織方式
特點:
1.每個分支節點至少有m棵子樹
2.根節點或者沒有子樹,或者至少有兩顆子樹
3.除根節點,其它每個分支節點至少有m/2(上界)棵子樹
4.有n棵子樹的節點有n個關鍵字
5.所有葉子節點包含全部關鍵字及指向相應記錄的指針
6.所有分支節點只是起到索引的作用。

ps:
1.每一個葉子節點都有一個指針,指向下一個數據,形成一個有序鏈表
2.只有葉子節點才有data,其它都是索引
B+樹和B樹的區別
1.有k個子節點的節點必然有k個關鍵字
2.非葉子節點僅具有索引作用,跟記錄有關的信息均存放在葉子節點中
3.樹的所有葉節點構成一個有序鏈表,可以按照關鍵字的次序遍歷全部記錄
B+樹的優點
1.B樹查找並不穩定(最好的情況是查詢根節點,最壞的情況是查詢葉子節點),而B+樹的每次查找都是穩定的。
2.由於B+樹中間節點沒有data數據,所以相同大小的磁盤頁可以容納更多的節點元素,所以數據量相同的情況下,B+樹比B樹更矮胖,查詢次數更少
所以,比起B樹,B+樹查詢性能很穩定,查詢范圍更廣
散列查找
哈希表
哈希表是一種線性存儲結構,記錄關鍵字與存儲地址存在某種函數關系的結構
裝填因子=存儲的記錄個數/哈希表的大小,哈希表長度和裝填因子有關,裝填因子越小,沖突可能性就越小
哈希表的構造方法
直接定址法
哈希函數:h(k)=k+c
優點:
函數計算簡單,並且不會存在哈徐沖突的情況
缺點:
1.當大數據可能分布不是那么集中時,直接定址法會造成空間的大量浪費
除留余數法
除留余數法是用關鍵字k除以某個不大於哈希表長度m的數p(最好是素數——所得的余數作為哈希地址的方法。
哈希函數:h(k)=k mod p
例題:
將關鍵字序列{7,8,30,11,18,9,14}散列存儲到散列表中,散列表的存儲空間是一個下標從0開始的一維數組,散列函數為:H(key)=(key×3) mod 7,要求裝填(載)因子為0.7。畫出所構造的散列表。
解題步驟:
第一步:算出表長:m=p/裝填因子,p是模,即7.所以m=10.
第二部,按照哈希函數的關系,進行計算,並填表。
| 下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| 關鍵字 | 7,14 | 8 | 11,18 | 30,9 |
通過上面,在一些條件下,我們就可以發現,很容易引起沖突,於是我們進行改進
線性探測法解決沖突
d0=h(k); //要着重注意,這里面mod p ,p與m沒有任何關系
di=(di-1 + 1)mod m;
m指的是表長
當每次一發現沖突的時候,就繼續順着哈希表往下+1進行探索,直到不發生沖突
| 下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| 關鍵字 | 7 | 14 | 8 | 11 | 30 | 18 | 9 | |||
| 探測次數 | 1 | 2 | 1 | 1 | 1 | 3 | 3 |
算探測次數:
首先,計算表長m=10,這個很重要,因為接下來是%m,不能弄錯,
在計算探測次數的時候,是%表長,而計算不成功ASL時,是/p,而p是題目給的,跟表長沒關系
14
d0=14*3%7=0,位置上已經有7了,移到下一個,第一次算,是按哈數函數算,mod7
d1=(0+1)%10=3,發現位置,入座,第二次算,以及接下來,都是mod表長
cnt=2;
18
d0=18*3%7=5,有人了
d1=(5+1)%10=6,有人了
d2=(6+1)%10=7,沒人,可以坐了
探測平方法解決沖突
d0=h(k)
di(d0 +(-) i^2)mod m
查找的位置依次為d0, d0+1, d0-1,d0+4,d0-4,,,,,,,
特點:
可以避免出現堆積現象,它的缺點是不能糖茶到哈希表上的所有單元,但至少能探查一半
依舊是上面的題目
| 下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| 關鍵字 | 7 | 14 | 8 | 18 | 11 | 30 | 9 | |||
| 探測次數 | 1 | 2 | 1 | 3 | 1 | 1 | 2 |
首先,也是要計算m等於表長=10(根據裝填因子)
18
d0=18*3%7=5 有11
d1=(5+1)%10=6 有30
d2=(5-1)%10=4 沒人,可以坐
cnt=3
9
d0=9*3%7=6 有30
d1=(6+1)%10=7 沒人
線性探測法計算ASL
| 下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| 關鍵字 | 7 | 14 | 8 | 11 | 30 | 18 | 9 | |||
| 成功探測次數 | 1 | 2 | 1 | 1 | 1 | 3 | 3 | |||
| 不成功探測次數 | 3 | 2 | 1 | 2 | 1 | 5 | 4 | 3 | 2 | 1 |
ASL(成功)的計算范圍是在那些有記錄數據的地方,既然要成功,肯定就在他們當中找
ASL(成功)=(有數字的關鍵字探測總次數)/有數據的關鍵字總數
ASL=(1+2+1+1+3+3)/7
ps:/的是有數據的關鍵字總數,而不是表的長度10
ASL(不成功),其實是從每個下標(0開始)查找到沒有數據,即空的位置的次數,因為是不成功,所以是%p,肯定是在1~p-1的范圍內,即p,所以是/p,而不是/表長
ASL=(3+2+1+2+1+5+4+3+2+1)/7
如從下標0開始,遇到的第一個沒有數據的點是下標為2的時候,探測次數為3
從下標1開始,遇到第一個沒有數據的點是下標為2的時候,探測次數為2
........
拉鏈法解決沖突
把具有相同地址的關鍵字用一條鏈連在一起
例題:
設一組初始記錄關鍵字集合為(25,10,8,27,32,68),散列表的長度為8,散列函數H(k)=k mod 7,要求:

ASL(成功)是找到有關鍵字的鏈節點*比較次數/所有關鍵字節點的個數
ASL(成功)=(1+1+1+1+1+2)/6
ASL(不成功)是遍歷了整個哈希表(左邊的數組)和哈希鏈,也沒有找到我們要找到的數據
對於有一個節點的單鏈表,不成功需要一次的關鍵詞比較,總共有4個這樣的單鏈表
對於有兩個節點的鏈表,不成功需要兩次的關鍵字比較,總共有1個這樣的鏈表
ASL(不成功)=(4 * 1+1 * 2)/7
總結:
線性探測法算探測次數的時候,第一次是mod p,即按公式,接下來,是mod 表長,如果題目沒給,要根據裝填因子算
算ASL成功的時候,是/關鍵字的個數
算ASL不成功的時候,是/p,p是多少就是多少,和表長沒關系
哈希表代碼實現
哈希表結構體
#define MaxSize 100
#define NULLKEY -1
#define DELKEY -2
typedef char * InfoType ;
typedef struct{
int key; // 關鍵字域
InfoType data; //其他數據域
int count; //探查次數
}HashTable[MaxSize];
哈希表插入
int InsertHT(HashTable ha,int p,int k,int &n) //建表是邊輸入邊插入的過程,重復調用這個函數
{
int adr,i;
adr=k % p;
if(adr==NULLKEY || adr==DELKEY) //地址為空,可插入數據
{
ha[adr].key=k;
ha[adr].count=1;
}
else
{ i=1;//計算探測次數
while(ha[adr].key!=NULLKEY && ha[adr].key!=DELKEY)
{
adr=(adr+1) % m;
i++;
}//查找插入位置
ha[adr].key=k;
ha[adr].count=i; //找到插入位置
}
n++;
}
哈希表查找
int SearchHT(HashTable ha,int p,int k)
{
int i=0,adr;
adr=k % p;
while(ha[adr].key!=NULLKEY && ha[adr].key!=k)
{
adr=(adr+1) % m;//探查下一個地址
}
if(ha[adr].key==NULLKEY)
return -1;//地址為空,找不到
if(ha[adr].key==k)
return adr; //找到關鍵字k
else
return -1;
}
哈希表刪除
int DeleteHT(HashTable ha,int p,int k,int &n)
{
int adr;
adr=SearchHT(ha,p,k);
if(adr!=-1)
{
ha[adr].key=DELKEY;//邏輯刪除,該元素貼刪除標記
n--;
return 1;
}
else
return 0;
}
哈希鏈結構體
typedef struct _Node
{
int data;
struct Node*next;
}HashNode,*HashList;//鏈節點的結構體定義
哈希鏈初始化
void Init_hash(HashList hash)
{
int i;
Node *hash=new HashNode[HASHSIZE];
for(i=0; i<HASHSIZE; i++) {
hash[i].next = NULL;//初始化每個槽位
}
}
哈希鏈搜索
HashList Search_Hash(HashList &hash,int key ,int adr)
{
HashList p;
int i;
p=hash[adr].next;
while(p)
{
if(p->data==key)
{//有重復關鍵字出現
break;
}
p=p->next;
}
return p;
}
哈希鏈插入
void Insert_Hash(HashList &hash,int p)
{
int adr,key;
int i;
HashList p;
for(i=0;i<n;i++)
{
cin<<key;
adr=key%p;
p=Search_Hash(hash,key,adr);
if(p==NULL)
{
p=new HashNode;
p->data=key;
p->next=hash[adr].next;
hash[adr].next=p;
}
}
}
1.2談談你對查找的認識及學習體會
查找在我們生活中的應用非常廣泛,我們的搜索引擎,查找軟件的路徑,查找聊天記錄,因為太過平常,所以可能很少時候,會去想,世界每時每分都有新的信息傳到互聯網,而我們,只是在小小的搜索框里,就可以搜索到大部分的它們,並且是在我們按下回車的那個瞬間,為什么查找搜索的速度可以如此之快,查找這章,為我開啟了一小扇窗戶,讓我了解了不同數據的存儲結構,不同的數據結構,有不一樣的應用場合,有不一樣的搜索速度,通過樹結構演變而來的二叉搜索樹,平衡二叉樹,b樹,b+樹,都是為了適應時代的需求,我們該感嘆那些前人的智慧,我到現在都在為計算機里小小的二進制數字可以轉變成多種多樣的形式而感到神奇,向前人的智慧致敬。
2. PTA題目介紹
2.1 題目一:7-1是否完全二叉搜索樹

2.1.1 該題的設計思路
二叉搜索樹的特點:
左子樹<根節點<右子樹
完全二叉樹的特點:
樹中的結點按從上至下、從左到右的順序進行編號,如果編號為i(1≤i≤n)的結點與滿二叉樹中編號為i的結點在二叉樹中的位置相同,則這棵二叉樹稱為完全二叉樹。
首先來理解一下完全二叉樹,下面左圖是完全二叉樹,有圖不是完全二叉樹

接着,我們按照層次遍歷的方法把左圖和右圖分別入隊列進行觀察,遇到一個節點,就把它的左右孩子入隊,然后當前節點出隊。

通過上面,是否發現了一個特點,如果是完全二叉樹,遇到第一個NULL之后,后面就不會再出現非空節點,如果不是完全二叉樹,則在NULL后面會出現非空節點。
根據上面的思路,我們要做的步驟是
1.建立一棵二叉搜索樹,采用樹結構
2.需要隊列,來判斷是否完全二叉搜索樹
2.1.2偽代碼
int main()
{
BinTree T;
建立一棵二叉搜索樹,並返回根節點給T
如果是完全二叉樹
輸出層次遍歷序列
輸出yes
否則
輸出層次遍歷序列
輸出no
}
BinTree BuiddTree()
{
BinTree BST;
初始化BST為空,
輸入節點個數n
for i=0 to i=n-1
輸入節點關鍵字
並Insert到BST這棵樹中
}
BinTree Insert(BinTree BST, int X)
{
if BST是NULL
申請一個節點T,左右孩子置為空,並賦值為x
else if 當前節點BST對應的關鍵字比x小
遞歸往右孩子尋找
else 當前節點BST對應的關鍵字比x大
遞歸往左孩子尋找
return 根節點BST;
}
bool IsBinaryTree(BinTree t)
{
if 是空樹,返回true,空樹也是完全二叉樹
queue<BinTree>qu;
把根節點t入隊
while(t!=NULL)
{
取隊頭節點t,並出隊
把t的左右孩子節點入隊,直到遇到第一個NULL節點
}
while(隊列不空)
{
檢查后面是否有非空節點
取隊頭節點t,並出隊
if(t是非空節點)
return false;
}
}
void LevelderTraversal(BinTree t)
{
queue<BinTree>Levelqu;
取根節點入隊
while(隊列不空)
{
取隊頭節點temp,並出隊
if temp不為空
把左右孩子入隊
並輸出當前節點的關鍵字
}
}
2.1.3 PTA提交列表

Q1:思路錯誤,如何辨別完全二叉樹的代碼思路是錯的,如果葉子節點的最右邊是由關鍵字的,我的代碼弄出來會是個yes的答案,本來還想着,用一個n來存儲每一層的節點個數,對每一次層進行判斷,但是我覺得如果碰到大數據的情況,n的時間效率會非常差,而且本身這樣的思路效率也不好,於是后來引進了隊列
Q2:層次遍歷輸出序列控制好,輸出層次遍歷並沒有特定的一個函數,而是和判斷完全二叉樹一起的,所以會容易導致錯誤
2.1.4本題知識點
1.學會二叉搜索樹的建立
2.如何判斷完全二叉樹
3.本題時間復雜度:O(n)
2.1.5 代碼實現
我的代碼





同學代碼:

上面是同學代碼的核心部分,同學代碼整體上比我的簡潔很多,因為它可以在做判斷的時候,並輸出先序遍歷,思路和我的也不一樣。
同學思路:
1.flag用來判斷是否是完全二叉樹
2.end用來找出第一個葉子節點,或者只有左孩子的節點,如果end置為1,說明后面的都將是葉子節點,那么如果后面出現了節點有左孩子或者右孩子的,那么說明就不是完全二叉樹,如果出現了一個節點有右孩子而沒有左孩子,也是錯的。
!
2.2題目二:7-2 二叉搜索樹的最近公共祖先

2.2.1該題的設計思路
1.解決二叉搜索樹的先序遍歷來建樹
2.解決尋找最近公共祖先的問題
解決第一個問題:
第一種方法,根據先序遍歷的序列,按照按照二叉搜索樹的方法進行建樹,得到的先序遍歷和輸入的先序遍歷是一樣的
Tree Insert(Tree BST, int X)
{
if (BST == NULL)
{
Tree T = (Tree)malloc(sizeof(struct TreeNode));
T->Left = NULL;
T->Right = NULL;
T->Key = X;
return T;
}
else if (BST->Key < X)
{
BST->Right = Insert(BST->Right, X);
}
else if (BST->Key > X)
{
BST->Left = Insert(BST->Left, X);
}
return BST;
}
第二種辦法:
給的是先序遍歷的序列,即按照根左右的辦法進行遍歷,我們在學到樹的那一章,有學到根據先序遍歷中序遍歷來建一棵樹的遞歸算法,有人可能會很奇怪,因為這道題里,只給了先序遍歷的代碼,並沒有中序遍歷,事實上,這道題,還有一個隱藏條件,就是二叉搜索的特點,它讓我們不需要中序遍歷,就可以建出一棵樹,首先,先理解,我們遞歸建樹的最重要要素是什么,就是"根"節點,對於每進入一個新的遞歸,就把當前節點看成是我們還未建樹的根節點,那么,這個根節點的特點是啥,如果后面比他小的樹,就是它的左子樹,遇到的第一個比它大的數,就是它的右子樹。

解決第二個問題:

Tree CreateTree(Tree BST, int* preorder, int len)
{
int i;
if (len == 0) return NULL;
BST = (Tree)malloc(sizeof(TreeNode));
BST->Key = *preorder;
BST->Left = BST->Right = NULL;
for (i = 0; i < len; i++)
{
if (preorder[i] > BST->Key)
{
break;
}
}
BST->Left = CreateTree(BST->Left, preorder + 1, i - 1);
BST->Right = CreateTree(BST->Right, preorder + i, len - i);
return BST;
}
2.2.2偽代碼
int main()
{
Tree BST=NULL;
輸入n,N;
for i=1 to i=N
輸入先序序列
建立一棵二叉搜索樹,並返回給BST;
for i=1 to i=n
輸入u,v
返回查找公共祖先LCA的結果ans
如果ans=u,那么u是v的祖先
如果ans=v,那么v是u的祖先
如果既不等u也不等v,那么ans是u和v的祖先
end for
}
int LCA(Tree T, int u, int v)
{
flagL,flagR用來標記u,v是否在樹中,初始化為1
如果u不在樹中,flagL置為-1
如果v不在樹中,flagR置為-1
如果flagR,flagL都為-1,輸出提示,並放回error
如果flagR為-1,輸出提示,並放回error
如果flagL為-1,輸出e提示,並放回error
如果u>v,將u與v互換
while(T!=NULL)
{
if 樹節點小於u
在右子樹找
else if 樹節點大於v
在左子樹找
else
返回公共祖先
}
}
Tree CreateTree(Tree BST, int* preorder, int len)
{
申請一個節點BST,並對其進行賦值
for i=0 to i=len-1
在preorder里找到第一個大於BST->key的值,然后break
用來區分左右子樹
先遞歸建立左子樹,范圍是preorder+1到i-1
然后遞歸建立右子樹,范圍是preorder+i,len-i
}
2.2.3 PTA提交列表


Q:這道題,我一直過不起的是第三和第四個測試點,告訴我運行超時,老師在上課有講過思路,和他的差不多,不過有一個東西被我忽略了,就是建樹,我一直以為這道題建樹不是難點,卻沒想到這道題如果建樹沒建好,就會導致超時,我本來就是用上面的第一種方法建樹的,但是沒意思到時它出錯了,所以一直在LCA里做文章,卻都過不去,然后取網上查找了一下,看到一篇和我一樣錯誤的,說一直有兩個測試點過不去,他指出時因為建樹的不對,我這才清楚,想想這兩個建樹的方法,方法一,每次都要遍歷出一條路徑,在葉子節點插入,這樣,在數據量大的情況下,時間效率也很可怕,而方法二不用,因為總的算起來,他就遞歸len-1次,就是節點的個數
2.2.4 本題知識點
1.如何用二叉搜索樹的先序遍歷建樹,並且效率更高
2.查找兩個節點的最近公共祖先
3.學會二叉搜索樹查找代碼的實現
2.2.5 代碼實現





2.3題目三:7-5(哈希鏈) 航空公司VIP客戶查詢
2.3.1該題的設計思路
這道題的思路是建好哈希鏈就好,比較難的是對於數據的處理
1.對於身份證的處理,將字符串轉換成數字,然后根據除留余數法,將它作為地址注意一下變量的類型
2.建哈希鏈,將得到的新id作為地址,然后在對應地址的哈希鏈里查找,如果有,加上對應的dis值,如果沒有,就用頭插法進行插入
3.最后查找,也是根據轉換后的id進行轉換
2.3.2偽代碼
int main()
{
輸入個數n和k
for i=0 to i=MAX
對哈希鏈進行初始化
end for
for i=0 to i=n-1
輸入id和dis
將字符串id轉換成地址ID
指針temp,指向對應ID的第一個表頭節點
while(temp)
{
對這條鏈進行遍歷
如果有找到,加上對應的dis值
}
如果沒找到,new一個新節點,通過頭插法插入到地址ID對應的鏈里
end for
輸入num
for i=0 to i=num-1
輸入身份證id
將身份證轉換成數字ID
同樣temp指向地址ID對應的第一個表頭結點
while(temp)
{
遍歷,如果找到,輸出dis
}
沒找到,輸出No Info
end for
}
2.3.3 PTA提交列表


1.一開始,沒有用哈希的做法做,建了一個鄰接表,把身份證用string存儲起來,然后最后遍歷一遍。所以最后對於測試點1和測試點2超時了,於是后來換成了哈希鏈做
2.換成哈希鏈后有一個比較懵逼的地方,就是數據處理,可能是對於哈希建法還是特別熟悉,所以將身份證轉換成數字后,就有點懵逼不知道干啥了,忘記了除留余數法,當時還糾結數據這么大,怎么當下標,現在覺得被自己蠢哭了
2.3.4 本題知識點
1.這道題,容易超時的一個很大原因是用了c++的cin來輸入字符串類型,一開始我是不知道的,因為數據結構開始,就老師就推薦我們用cin,因為簡單,且它會自動識別類型,后來同學和我說,這道題不能用cin,因為會容易超時,后來我也去查看了網上的介紹,當數據量較大時,cin和cout的耗時可能會是scanf,printf的好幾倍
原因:
因為C++為了兼容C,保證程序在使用了std::printf和std::cout的時候不發生混亂,將輸出流綁到了一起,即cin綁定的是cout,每次執行 << 時都要調用flush,增加了IO負擔。這就意味着,先把要輸出的東西存入緩沖區,再輸出緩存,從而導致效率降低,如果我們還是想用cin,cout的話,只需要把他們解除綁定即可
#include <iostream>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);//接觸綁定
}
2.第二個知識點,就是對數據巧妙地處理,因為身份證中包含x,它不是數字,所以處理的時候,可以把它看成10,或11等等,單獨處理
2.3.5 代碼實現




