第一章 概 論
1.數據:信息的載體,能被計算機識別、存儲和加工處理。
2.數據元素:數據的基本單位,可由若干個數據項組成,數據項是具有獨立含義的最小標識單位。
3.數據結構:數據之間的相互關係,即數據的組織形式。
它包括:1)數據的邏輯結構,從邏輯關係上描述數據,與數據存儲無關,獨立於計算機;
2)數據的存儲結構,是邏輯結構用計算機語言的實現,依賴於計算機語言。
3)數據的運算,定義在邏輯結構上,每種邏輯結構都有一個運算集合。常用的運算:檢索/插入/刪除/更新/排序。
4.數據的邏輯結構可以看作是從具體問題抽象出來的數學模型。數據的存儲結構是邏輯結構用計算機語言的實現。
5.數據類型:一個值的集合及在值上定義的一組操作的總稱。分爲:原子類型和結構類型。
6.抽象數據類型:抽象數據的組織和與之相關的操作。優點:將數據和操作封裝在一起實現了信息隱藏。
7. 抽象數據類型ADT:是在概念層上描述問題;類:是在實現層上描述問題;在應用層上操作對象(類的實例)解決問題。
8.數據的邏輯結構,簡稱爲數據結構,有:
(1)線性結構,若結構是非空集則僅有一個開始和終端結點,並且所有結點最多隻有一個直接前趨和後繼。
(2)非線性結構,一個結點可能有多個直接前趨和後繼。
9.數據的存儲結構有:
1)順序存儲,把邏輯相鄰的結點存儲在物理上相鄰的存儲單元內。
2)鏈接存儲,結點間的邏輯關係由附加指針字段表示。
3)索引存儲,存儲結點信息的同時,建立附加索引表,有稠密索引和稀疏索引。
4)散列存儲,按結點的關鍵字直接計算出存儲地址。
10.評價算法的好壞是:算法是正確的;執行算法所耗的時間;執行算法的存儲空間(輔助存儲空間);易於理解、編碼、調試。
11.算法的時間複雜度T(n):是該算法的時間耗費,是求解問題規模n的函數。記爲O(n)。
時間複雜度按數量級遞增排列依次爲:常數階O(1)、對數階O(log2n)、線性階O(n)、線性對數階O(nlog2n)、平方階O(n^2)、立方階O(n^3)、……k次方階O(n^k)、指數階O(2^n)。13.算法的空間複雜度S(n):是該算法的空間耗費,是求解問題規模n的函數。
12.算法衡量:是用時間複雜度和空間複雜度來衡量的,它們合稱算法的複雜度。
13. 算法中語句的頻度不僅與問題規模有關,還與輸入實例中各元素的取值相關。
第 二 章 線 性 表
1.線性表:是由n(n≥0)個數據元素組成的有限序列。
2.線性表的基本運算有:
1)InitList(L),構造空表,即表的初始化;
2)ListLength(L),求表的結點個數,即表長;
3)GetNode(L,i),取表中第i個結點,要求1≤i≤ListLength(L);
4)LocateNode(L,x)查找L中值爲x的結點並返回結點在L中的位置,有多個x則返回首個,沒有則返回特殊值表示查找失敗。
5)InsertList(L,x,i)在表的第i個位置插入值爲x的新結點,要求1≤i≤ListLength(L)+1;
6)DeleteList(L,i)刪除表的第i個位置的結點,要求1≤i≤ListLength(L);
3.順序表:把線性表的結點按邏輯次序存放在一組地址連續的存儲單元裏。
4.順序表結點的存儲地址計算公式:Loc(ai)=Loc(a1)+(i-1)*C;1≤i≤n
5.順序表上的基本運算
(1)插入
void insertlist(seqlist *L,datatype x,int i)
{
int j;
if(i<1||i>L->length+1)
error(“position error”);
if(L->length>=listsize)
error(“overflow”);
for(j=L->length-1;j>=i-1;j--)
L->data[j+1]=L->data[j]; 結點後移
L->data[i-1]=x;
L->length++;
}
在順序表上插入要移動表的n/2結點,算法的平均時間複雜度爲O(n)。
(2)刪除
void delete (seqlist *L,int i)
{
int j;
if(i<1||i>L->length)
error(“position error”);
for(j=i;j<=L->length-1;j++)
L->data[j-1]=L->data[j]; 結點前移
L->length--;
}
在順序表上刪除要移動表的(n+1)/2結點,算法的平均時間複雜度爲O(n)。
6.單鏈表:只有一個鏈域的鏈表稱單鏈表。
在結點中存儲結點值和結點的後繼結點的地址,data next data是數據域,next是指針域。
(1)建立單鏈表。時間複雜度爲O(n)。
加頭結點的優點:1)鏈表第一個位置的操作無需特殊處理;2)將空表和非空表的處理統一。
(2)查找運算。時間複雜度爲O(n)。
1) 按序號查找。
Listnode * getnode(linklist head,int i)
{
int j;
listnode *p;
p=head;j=0;
while(p->next&&j<i){
p=p->next; 指針下移
j++;
}
if(i==j)
return p;
else
return NULL;
}
2) 按值查找。
Listnode * locatenode(linklist head ,datatype key)
{
listnode *p=head->next;
while(p&&p->data!=key)
p=p->next;
return p;
}
(3)插入運算。時間複雜度爲O(n)。
Void insertlist(linklist head ,datatype x, int i)
{
listnode *p;
p=getnode(head,i-1);
if(p==NULL);
error(“position error”);
s=(listnode *)malloc(sizeof(listnode));
s->data=x;
s->next=p->next;
p->next=s;
}
(4) 刪除運算。時間複雜度爲O(n)。
Void deletelist(linklist head ,int i)
{
listnode *p ,*r;
p=getnode(head ,i-1);
if(p==NULL||p->next==NULL)
error(“position error”);
r=p->next;
p->next=r->next;
free(r);
}
7.循環鏈表:是一種首尾相連的鏈表。特點是無需增加存儲量,僅對錶的鏈接方式修改使表的處理靈活方便。
8.空循環鏈表僅由一個自成循環的頭結點表示。
9.很多時候表的操作是在表的首尾位置上進行,此時頭指針表示的單循環鏈表就顯的不夠方便,改用尾指針rear來表示單循環鏈表。用頭指針表示的單循環鏈表查找開始結點的時間是O(1),查找尾結點的時間是O(n);用尾指針表示的單循環鏈表查找開始結點和尾結點的時間都是O(1)。
10.在結點中增加一個指針域,prior|data|next。形成的鏈表中有兩條不同方向的鏈稱爲雙鏈表。
1) 雙鏈表的前插操作。時間複雜度爲O(1)。
Void dinsertbefore(dlistnode *p ,datatype x)
{
dlistnode *s=malloc(sizeof(dlistnode));
s->data=x;
s->prior=p->prior;
s->next=p;
p->prior->next=s;
p->prior=s;
}
2) 雙鏈表的刪除操作。時間複雜度爲O(1)。
Void ddeletenode(dlistnode *p)
{
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);
}
11.順序表和鏈表的比較
1) 基於空間的考慮:順序表的存儲空間是靜態分配的,鏈表的存儲空間是動態分配的。順序表的存儲密度比鏈表大。因此,在線性表長度變化不大,易於事先確定時,宜採用順序表作爲存儲結構。
2) 基於時間的考慮:順序表是隨機存取結構,若線性表的操作主要是查找,很少有插入、刪除操作時,宜用順序表結構。對頻繁進行插入、刪除操作的線性表宜採用鏈表。若操作主要發生在表的首尾時採用尾指針表示的單循環鏈表。
12.存儲密度=(結點數據本身所佔的存儲量)/(整個結點結構所佔的存儲總量)
存儲密度:順序表=1,鏈表<1。
第 三 章 棧 和 隊 列
1.棧是限制僅在表的一端進行插入和刪除運算的線性表又稱爲後進先出表(LIFO表)。插入、刪除端稱爲棧頂,另一端稱棧底。表中無元素稱空棧。
2.棧的基本運算有:
1) initstack(s),構造一個空棧;
2) stackempty(s),判棧空;
3) stackfull(s),判棧滿;
4) push(s,x),進棧;
5) pop (s),退棧;
6) stacktop(s),取棧頂元素。
3.順序棧:棧的順序存儲結構稱順序棧。
4.當棧滿時,做進棧運算必定產生空間溢出,稱“上溢”。 當棧空時,做退棧運算必定產生空間溢出,稱“下溢”。上溢是一種錯誤應設法避免,下溢常用作程序控制轉移的條件。
5.在順序棧上的基本運算:
1) 置空棧。
Void initstack(seqstack *s)
{
s->top=-1;
}
2)判棧空。
int stackempty(seqstack *s)
{
return s->top==-1;
}
3)判棧滿。
int stackfull(seqstack *s)
{
return s->top==stacksize-1;
}
4)進棧。
Void push(seqstack *s,datatype x)
{
if(stackfull(s))
error(“stack overflow”);
s->data[++s->top]=x;
}
5)退棧。
Datatype pop(seqstack *s)
{
if(stackempty(s))
error(“stack underflow”);
return S->data[s->top--];
}
6)取棧頂元素。
Dtatatype stacktop(seqstack *s)
{
if(stackempty(s))
error(“stack underflow”);
return S->data[s->top];
}
6.鏈棧:棧的鏈式存儲結構稱鏈棧。棧頂指針是鏈表的頭指針。
7.鏈棧上的基本運算:
1) 建棧。
Void initstack(linkstack *s)
{
s->top=NULL;
}
2)判棧空。
Int stackempty (linkstack *s)
{
return s->top==NULL;
}
3) 進棧。
Void push(linkstack *s,datatype x)
{
stacknode *p=(stacknode *)malloc(sizeof(stacknode));
p->data=x;
p->next=s->top;
s->top=p;
}
4) 退棧。
Datatype pop(linksatck *s)
{
datatype x;
stacknode *p=s->top;
if(stackempty(s))
error(“stack underflow”);
x=p->data;
s->top=p->next;
free(p);
return x;
}
5) 取棧頂元素。
Datatype stacktop(linkstack *s)
{
if(stackempty(s))
error(“stack is empty”);
return s->top->data;
}
8.隊列是一種運算受限的線性表,允許刪除的一端稱隊首,允許插入的一端稱隊尾。隊列又稱爲先進先出線性表,FIFO表。
9.隊列的基本運算:
1) initqueue(q),置空隊;
2) queueempty(q),判隊空;
3) queuefull(q),判隊滿;
4) enqueue(q,x),入隊;
5) dequeue(q),出隊;
6) queuefront(q),返回隊頭元素。
10.順序隊列:隊列的順序存儲結構稱順序隊列。設置front和rear指針表示隊頭和隊尾元素在向量空間的位置。
11.順序隊列中存在“假上溢”現象,由於入隊和出隊操作使頭尾指針只增不減導致被刪元素的空間無法利用,隊尾指針超過向量空間的上界而不能入隊。
12.爲克服“假上溢”現象,將向量空間想象爲首尾相連的循環向量,存儲在其中的隊列稱循環隊列。i=(i+1)%queuesize
13.循環隊列的邊界條件處理:由於無法用front==rear來判斷隊列的“空”和“滿”。
解決的方法有:
1) 另設一個布爾變量以區別隊列的空和滿;
2) 少用一個元素,在入隊前測試rear在循環意義下加1是否等於front;
3) 使用一個記數器記錄元素總數。
14.循環隊列的基本運算:
1) 置隊空。
Void initqueue(cirqueue *q)
{
q->front=q->rear=0;
q->count=0;
}
2) 判隊空。
Int queueempty(cirqueue *q)
{
return q->count==0;
}
3) 判隊滿。
Int queuefull(cirqueue *q)
{
return q->count==queuesize;
}
4) 入隊。
Void enqueue(cirqueue *q ,datatype x)
{
if(queuefull(q))
error(“queue overfolw”);
q->count++;
q->data[q->rear]=x;
q->rear=(q->rear+1)%queuesize;
}
5) 出隊。
Datatype dequeue(cirqueue *q)
{
datatype temp;
if(queueempty(q))
error(“queue underflow”);
temp=q->data[q->front];
q->count--;
q->front=(q->front+1)%queuesize;
return temp;
}
6) 取隊頭元素。
Datatype queuefront(cirqueue *q)
{
if(queueempty(q))
error(“queue is empty”);
return q->data[q->front];
}
15.鏈隊列:隊列的鏈式存儲結構稱鏈隊列,鏈隊列由一個頭指針和一個尾指針唯一確定。
16.鏈隊列的基本運算:
1) 建空隊。
Void initqueue(linkqueue *q)
{
q->front=q->rear=NULL;
}
2) 判隊空。
Int queueempty(linkqueue *q)
{
return q->front==NULL&&q->rear==NULL;
}
3) 入隊。
Void enqueue(linkqueue *q,datatype x)
{
queuenode *p=(queuenode *)malloc(sizeof(queuenode));
p->data=x;
p->next=NULL;
if(queueempty(q))
q-front=q->rear=p;
else{
q->rear->next=p;
q->rear=p;
}
}
4) 出隊。
Datatype dequeue(linkqueue *q)
{
datatype x;
queuenode *p;
if(queueempty(q))
error(“queue is underflow”);
p=q->front;
x=p->data;
q->front=p->next;
if(q->rear==p) q->rear=NULL;
free(p);
return x;
}
5) 取隊頭元素。
Datatype queuefront(linkqueue *q)
{
if(queueempty(q))
error(“queue is empty”);
return q->front->data;
}
第 四 章 串
1.串:是由零個或多個字符組成的有限序列;包含字符的個數稱串的長度;
2.空串:長度爲零的串稱空串; 空白串:由一個或多個空格組成的串稱空白串;
子串:串中任意個連續字符組成的子序列稱該串的子串; 主串:包含子串的串稱主串;
子串的首字符在主串中首次出現的位置定義爲子串在主串中的位置;
3.空串是任意串的子串; 任意串是自身的子串;
串常量在程序中只能引用但不能改變其值; 串變量取值可以改變;
4.串的基本運算
1) int strlen(char *s);求串長。
2) char *strcpy(char * to,char * from);串複製。
3) char *strcat(char * to,char * from);串聯接。
4) int strcmp(char *s1,char *s2);串比較。
5) char *strchr(char *s,char c);字符定位。
5.串的存儲結構:
(1)串的順序存儲:串的順序存儲結構稱順序串。按存儲分配不同分爲:
1) 靜態存儲分配的順序串:
直接用定長的字符數組定義,以“\0”表示串值終結。
#define maxstrsize 256
typedef char seqstring[maxstrsize];
seqstring s;
不設終結符,用串長表示。
Typedef struct{
Char ch[maxstrsize];
Int length;
}seqstring;
以上方式的缺點是:串值空間大小是靜態的,難以適應插入、鏈接等操作。
2) 動態存儲分配的順序串:
簡單定義:typedef char * string;
複雜定義:typedef struct{
char *ch;
int length;
}hstring;
(2)串的鏈式存儲:串的鏈式存儲結構稱鏈串。鏈串由頭指針唯一確定。類型定義:
typedef struct node{
char data;
struct node *next;
}linkstrnode;
typedef linkstrnode *linkstring;
linkstring s;
將結點數據域存放的字符個數定義爲結點的大小。結點大小不爲1的鏈串類型定義:
#define nodesize 80
typedef struct node{
char data[nodesize];
struct node * next;
}linkstrnode;
6.串運算的實現
(1)順序串上的子串定位運算。
1)子串定位運算又稱串的模式匹配或串匹配。主串稱目標串;子串稱模式串。
2)樸素的串匹配算法。時間複雜度爲O(n^2)。比較的字符總次數爲(n-m+1)m。
Int naivestrmatch(seqstring t,seqstring p)
{
int i,j,k;
int m=p.length;
int n=t.length;
for(i=0;i<=n-m;i++){
j=0;k=i;
while(j<m&&t.ch[k]==p.ch[j]){
j++;k++;
}
if (j==m) return i;
}
return –1;
}
(2)鏈串上的子串定位運算。時間複雜度爲O(n^2)。比較的字符總次數爲(n-m+1)m。
Linkstrnode * lilnkstrmatch(linkstring T, linkstring P)
{
linkstrnode *shift, *t, *p;
shift=T;
t=shift;p=P;
while(t&&p){
if(t->data==p->data){
t=t->next;
p=p->next;
}
else{
shift=shift->next;
t=shift;
p=P;
}
}
if(p==NULL)
return shift;
else
return NULL;
}
第 五 章 多 維 數 組 和 廣 義 表
1.多維數組:一般用順序存儲的方式表示數組。
2.常用方式有:1)行優先順序,將數組元素按行向量排列;
2)列優先順序,將數組元素按列向量排列。
3.計算地址的函數:LOC(Aij)=LOC(Ac1c2)+((i-c1)*(d2-c2+1)+j-c2)*d
4.矩陣的壓縮存儲:爲多個非零元素分配一個存儲空間;對零元素不分配存儲空間。
(1) 對稱矩陣:在一個n階的方陣A中,元素滿足Aij=Aji 0<=i,j<=n-1;稱爲對稱矩陣。
元素的總數爲:n(n+1)/2;
設:I=i或j中大的一個數;J=i或j中小的一個數;
則:k=I*(I+1)/2+J;
地址計算:LOC(Aij)=LOC(sa[k])=LOC(sa[0])+k*d= LOC(sa[0])+ (I*(I+1)/2+J )*d
(2)三角矩陣:以主對角線劃分,三角矩陣有上三角和下三角;上三角的主對角線下元素均爲常數c;下三角的主對角線上元素均爲常數c。
元素總數爲:(n(n+1)/2)+1;
以行優先順序存放的Aij與SA[k]的關係:
上三角陣:k=i*(2n-i+1)/2+j-i;
下三角陣:k=i*(i+1)/2+j;
(3)對角矩陣:所有的非零元素集中在以主對角線爲中心的帶狀區域,相鄰兩側元素均爲零。|i-j|>(k-1)/2
以行優先順序存放的Aij與SA[k]的關係:k=2i+j;
5.稀疏矩陣:當矩陣A中有非零元素S個,且S遠小於元素總數時,稱爲稀疏矩陣。
對其壓縮的方法有順序存儲和鏈式存儲。
(1)三元組表:將表示稀疏矩陣的非零元素的三元組(行號、列號、值)按行或列優先的順序排列得到的一個結點均是三元組的線性表,將該表的線性存儲結構稱爲三元組表。其類型定義:
#define maxsize 10000
typedef int datatype;
typedef struct{
int i,j;
datatype v;
}trituplenode;
typedef struct{
trituplenode data[maxsize];
int m,n,t;
}tritupletable;
(2)帶行表的三元組表:在按行優先存儲的三元組表中加入一個行表記錄每行的非零元素在三元組表中的起始位置。類型定義:
#define maxrow 100
typedef struct{
tritulpenode data[maxsize];
int rowtab[maxrow];
int m, n, t;
}rtritulpetable;
6.廣義表:是線性表的推廣,廣義表是n個元素的有限序列,元素可以是原子或一個廣義表,記爲LS。
7.若元素是廣義表稱它爲LS的子表。若廣義表非空,則第一個元素稱表頭,其餘元素稱表尾。
8.表的深度是指表展開後所含括號的層數。
9.把與樹對應的廣義表稱爲純表,它限制了表中成分的共享和遞歸;
10.允許結點共享的表稱爲再入表;
11.允許遞歸的表稱爲遞歸表;
12.相互關係:線性表∈純表∈再入表∈遞歸表;
13.廣義表的特殊運算:1)取表頭head(LS);2)取表尾tail(LS);
第 六 章 樹
1.樹:是n個結點的有限集T,T爲空時稱空樹,否則滿足:
1)有且僅有一個特定的稱爲根的結點;
2)其餘結點可分爲m個互不相交的子集,每個子集本身是一棵樹,並稱爲根的子樹。
2.樹的表示方法:1)樹形表示法;2)嵌套集合表示法;3)凹入表表示法;4)廣義表表示法;
3.一個結點擁有的子樹數稱爲該結點的度;一棵樹的度是指樹中結點最大的度數。
4.度爲零的結點稱葉子或終端結點;度不爲零的結點稱分支結點或非終端結點
5.根結點稱開始結點,根結點外的分支結點稱內部結點;
6.樹中某結點的子樹根稱該結點的孩子;該結點稱爲孩子的雙親;
7.樹中存在一個結點序列K1,K2,…Kn,使Ki爲Ki+1的雙親,則稱該結點序列爲K1到Kn的路徑或道路;
8.樹中結點K到Ks間存在一條路徑,則稱K是Ks的祖先,Ks是K的子孫;
9.結點的層數從根算起,若根的層數爲1,則其餘結點層數是其雙親結點層數加1;雙親在同一層的結點互爲堂兄弟;樹中結點最大層數稱爲樹的高度或深度;
10.樹中每個結點的各個子樹從左到右有次序的稱有序樹,否則稱無序樹;
11.森林是m棵互不相交的樹的集合。
12.二叉樹:是n個結點的有限集,它或爲空集,或由一個根結點及兩棵互不相交的、分別稱爲該根的左子樹和右子樹的二叉樹組成。
13.二叉樹不是樹的特殊情況,這是兩種不同的數據結構;它與無序樹和度爲2的有序樹不同。
14.二叉樹的性質:
1) 二叉樹第i層上的結點數最多爲2^(i-1);
2) 深度爲k的二叉樹至多有2^k-1個結點;
3) 在任意二叉樹中,葉子數爲n0,度爲2的結點數爲n2,則n0=n2+1;
15.滿二叉樹是一棵深度爲k的且有2^k-1個結點的二叉樹;
16.完全二叉樹是至多在最下兩層上結點的度數可以小於2,並且最下層的結點集中在該層最左的位置的二叉樹;
17.具有N個結點的完全二叉樹的深度爲log2N取整加1;
18.二叉樹的存儲結構
(1)順序存儲結構:把一棵有n個結點的完全二叉樹,從樹根起自上而下、從左到右對所有結點編號,然後依次存儲在一個向量b[0~n]中,b[1~n]存放結點,b[0]存放結點總數。
各個結點編號間的關係:
1) i=1是根結點;i>1則雙親結點是i/2取整;
2) 左孩子是2i, 右孩子是2i+1;(要小於n)
3) i>(n/2取整)的結點是葉子;
4) 奇數沒有右兄弟,左兄弟是i-1;
5) 偶數沒有左兄弟,右兄弟是i+1;
(2)鏈式存儲結構
結點的結構爲:lchild|data|rchild ;相應的類型說明:
typedef char data;
typedef struct node{
datatype data;
structnode *lchild , *rchild;
}bintnode;
typedef bintnode * bintree;
19.在二叉樹中所有類型爲bintnode的結點和一個指向開始結點的bintree類型的頭指針構成二叉樹的鏈式存儲結構稱二叉鏈表。
20.二叉鏈表由根指針唯一確定。在n個結點的二叉鏈表中有2n個指針域,其中n+1個爲空。
21.二叉樹的遍歷方式有:前序遍歷、中序遍歷、後序遍歷。時間複雜度爲O(n)。
22.線索二叉樹:利用二叉鏈表中的n+1個空指針域存放指向某種遍歷次序下的前趨和後繼結點的指針,這種指針稱線索。加線索的二叉鏈表稱線索鏈表。相應二叉樹稱線索二叉樹。
23.線索鏈表結點結構:lchild|ltag|data|rtag|rchild;ltag=0,lchild是指向左孩子的指針;ltag=1,lchild是指向前趨的線索;rtag=0,rchild是指向右孩子的指針;rtag=1,rchild是指向後繼的線索;
24.查找*p在指定次序下的前趨和後繼結點。算法的時間複雜度爲O(h)。線索對查找前序前趨和後序後繼幫助不大。
25.遍歷線索二叉樹。時間複雜度爲O(n)。
26.樹、森林與二叉樹的轉換
(1)樹、森林與二叉樹的轉換
1)樹與二叉樹的轉換:1}所有兄弟間連線;2}保留與長子的連線,去除其它連線。該二叉樹的根結點的右子樹必爲空。
2)森林與二叉樹的轉換:1}將所有樹轉換成二叉樹;2}將所有樹根連線。
(2)二叉樹與樹、森林的轉換。是以上的逆過程。
27.樹的存儲結構
(1)雙親鏈表表示法:爲每個結點設置一個parent指針,就可唯一表示任何一棵樹。Data|parent
(2)孩子鏈表表示法:爲每個結點設置一個firstchild指針,指向孩子鏈表頭指針,鏈表中存放孩子結點序號。Data|firstchild。
(3雙親孩子鏈表表示法:將以上方法結合。Data|parent|firstchild
(4)孩子兄弟鏈表表示法:附加兩個指向左孩子和右兄弟的指針。Leftmostchild|data|rightsibling
28.樹和森林的遍歷:前序遍歷一棵樹等價於前序遍歷對應二叉樹;後序遍歷等價於中序遍歷對應二叉樹。
29.最優二叉樹(哈夫曼樹):樹的路徑長度是從樹根到每一結點的路徑長度之和。將樹中的結點賦予實數稱爲結點的權。
30.結點的帶權路徑是該結點的路徑長度與權的乘積。樹的帶權路徑長度又稱樹的代價,是所有葉子的帶權路徑長度之和。
31.帶權路徑長度最小的二叉樹稱最優二叉樹(哈夫曼樹)。
32.具有2n-1個結點其中有n個葉子,並且沒有度爲1的分支結點的樹稱爲嚴格二叉樹。
33.哈夫曼編碼
34.對字符集編碼時,要求字符集中任一字符的編碼都不是其它字符的編碼前綴,這種編碼稱前綴碼。
35.字符出現頻度與碼長乘積之和稱文件總長;字符出現概率與碼長乘積之和稱平均碼長;
36.使文件總長或平均碼長最小的前綴碼稱最優前綴碼
37.利用哈夫曼樹求最優前綴碼,左爲0,右爲1。編碼平均碼長最小;沒有葉子是其它葉子的祖先,不可能出現重複前綴。
第 七 章 圖
1.圖:圖G是由頂點集V和邊集E組成,頂點集是有窮非空集,邊集是有窮集;
2.G中每條邊都有方向稱有向圖;有向邊稱弧;邊的始點稱弧尾;邊的終點稱弧頭;G中每條邊都沒有方向的稱無向圖。
3.頂點n與邊數e的關係:無向圖的邊數e介於0~n(n-1)/2之間,有n(n-1)/2條邊的稱無向完全圖;有向圖的邊數e介於0~n(n-1)之間,有n(n-1)條邊的稱有向完全圖;
4.無向圖中頂點的度是關聯與頂點的邊數;有向圖中頂點的度是入度與出度的和。
所有圖均滿足:所有頂點的度數和的一半爲邊數。
5.圖G(V,E),如V’是V的子集,E’是E的子集,且E’中關聯的頂點均在V’中,則G’(V’,E’)是G的子圖。
6.在有向圖中,從頂點出發都有路徑到達其它頂點的圖稱有根圖;
7.在無向圖中,任意兩個頂點都有路徑連通稱連通圖;極大連通子圖稱連通分量;
8.在有向圖中,任意順序兩個頂點都有路徑連通稱強連通圖;極大連通子圖稱強連通分量;
9.將圖中每條邊賦上權,則稱帶權圖爲網絡。
10.圖的存儲結構:
(1)鄰接矩陣表示法:鄰接矩陣是表示頂點間相鄰關係的矩陣。n個頂點就是n階方陣。
無向圖是對稱矩陣;有向圖行是出度,列是入度。
(2)鄰接表表示法:對圖中所有頂點,把與該頂點相鄰接的頂點組成一個單鏈表,稱爲鄰接表,adjvex|next,如要保存頂點信息加入data;對所有頂點設立頭結點,vertex|firstedge,並順序存儲在一個向量中;vertex保存頂點信息,firstedge保存鄰接表頭指針。
11.鄰接矩陣表示法與鄰接表表示法的比較:
1) 鄰接矩陣是唯一的,鄰接表不唯一;
2) 存儲稀疏圖用鄰接表,存儲稠密圖用鄰接矩陣;
3) 求無向圖頂點的度都容易,求有向圖頂點的度鄰接矩陣較方便;
4) 判斷是否是圖中的邊,鄰接矩陣容易,鄰接表最壞時間爲O(n);
5) 求邊數e,鄰接矩陣耗時爲O(n^2),與e無關,鄰接表的耗時爲O(e+n);
12.圖的遍歷:
(1)圖的深度優先遍歷:類似與樹的前序遍歷。按訪問頂點次序得到的序列稱DFS序列。
對鄰接表表示的圖深度遍歷稱DFS,時間複雜度爲O(n+e); 對鄰接矩陣表示的圖深度遍歷稱DFSM,時間複雜度爲O(n^2);
(2)圖的廣度優先遍歷:類似與樹的層次遍歷。按訪問頂點次序得到的序列稱BFS序列。
對鄰接表表示的圖廣度遍歷稱BFS,時間複雜度爲O(n+e); 對鄰接矩陣表示的圖廣度遍歷稱BFSM,時間複雜度爲O(n^2);
13. 將沒有迴路的連通圖定義爲樹稱自由樹。
14.生成樹:連通圖G的一個子圖若是一棵包含G中所有頂點的樹,該子圖稱生成樹。
有DFS生成樹和BFS生成樹,BFS生成樹的高度最小。
非連通圖生成的是森林。
15.最小生成樹:將權最小的生成樹稱最小生成樹。(是無向圖的算法)
(1)普里姆算法:
1) 確定頂點S、初始化候選邊集T[0~n-2];formvex|tovex|lenght
2) 選權值最小的T[i]與第1條記錄交換;
3) 從T[1]中將tovex取出替換以下記錄的fromvex計算權;若權小則替換,否則不變;
4) 選權值最小的T[i]與第2條記錄交換;
5) 從T[2]中將tovex取出替換以下記錄的fromvex計算權;若權小則替換,否則不變;
6) 重複n-1次。
初始化時間是O(n),選輕邊的循環執行n-1-k次,調整輕邊的循環執行n-2-k;算法的時間複雜度爲O(n^2),適合於稠密圖。
(2)克魯斯卡爾算法:
1) 初始化確定頂點集和空邊集;對原邊集按權值遞增順序排序;
2) 取第1條邊,判斷邊的2個頂點是不同的樹,加入空邊集,否則刪除;
3) 重複e次。
對邊的排序時間是O(elog2e);初始化時間爲O(n);執行時間是O(log2e);算法的時間複雜度爲O(elog2e),適合於稀疏圖。
16. 路徑的開始頂點稱源點,路徑的最後一個頂點稱終點;
17.單源最短路徑問題:已知有向帶權圖,求從某個源點出發到其餘各個頂點的最短路徑;
18.單目標最短路徑問題:將圖中每條邊反向,轉換爲單源最短路徑問題;
19.單頂點對間最短路徑問題:以分別對不同頂點轉換爲單源最短路徑問題;
20.所有頂點對間最短路徑問題:分別對圖中不同頂點對轉換爲單源最短路徑問題;
21.迪傑斯特拉算法:
1) 初始化頂點集S[i],路徑權集D[i],前趨集P[i];
2) 設置S[s]爲真,D[s]爲0;
3) 選取D[i]最小的頂點加入頂點集;
4) 計算非頂點集中頂點的路徑權集;
5) 重複3)n-1次。
算法的時間複雜度爲O(n^2)。
22.拓撲排序:對一個有向無環圖進行拓撲排序,是將圖中所有頂點排成一個線性序列,滿足弧尾在弧頭之前。這樣的線性序列稱拓撲序列。
(1)無前趨的頂點優先:總是選擇入度爲0的結點輸出並刪除該頂點的所有邊。
設置各個頂點入度時間是O(n+e),設置棧或隊列的時間是O(n),算法時間複雜度爲O(n+e)。
(2)無後繼的頂點優先:總是選擇出度爲0的結點輸出並刪除該頂點的所有邊。
設置各個頂點出度時間是O(n+e),設置棧或隊列的時間是O(n),算法時間複雜度爲O(n+e)。
求得的是逆拓撲序列。
第 八 章 排 序
1.文件:由一組記錄組成,記錄有若干數據項組成,唯一標識記錄的數據項稱關鍵字;
2.排序是將文件按關鍵字的遞增(減)順序排列;
3.排序文件中有相同的關鍵字時,若排序後相對次序保持不變的稱穩定排序,否則稱不穩定排序;
4.在排序過程中,文件放在內存中處理不涉及數據的內、外存交換的稱內排序,反之稱外排序;
5.排序算法的基本操作:1)比較關鍵字的大小;2)改變指向記錄的指針或移動記錄本身。
6.評價排序方法的標準:1)執行時間;2)所需輔助空間,輔助空間爲O(1)稱就地排序;另要注意算法的複雜程度。
7.若關鍵字類型沒有比較運算符,可事先定義宏或函數表示比較運算。
8.插入排序
(1)直接插入排序
算法中引入監視哨R[0]的作用是:1)保存R[i]的副本;2)簡化邊界條件,防止循環下標越界。
關鍵字比較次數最大爲(n+2)(n-1)/2;記錄移動次數最大爲(n+4)(n-1)/2;
算法的最好時間是O(n);最壞時間是O(n^2);平均時間是O(n^2);是一種就地的穩定的排序;
(2)希爾排序
實現過程:是將直接插入排序的間隔變爲d。d的取值要注意:1)最後一次必爲1;2)避免d值互爲倍數;
關鍵字比較次數最大爲n^1.25;記錄移動次數最大爲1.6n^1.25;
算法的平均時間是O(n^1.25);是一種就地的不穩定的排序;
9.交換排序
(1)冒泡排序
實現過程:從下到上相鄰兩個比較,按小在上原則掃描一次,確定最小值,重複n-1次。
關鍵字比較次數最小爲n-1、最大爲n(n-1)/2;記錄移動次數最小爲0,最大爲3n(n-1)/2;
算法的最好時間是O(n);最壞時間是O(n^2);平均時間是O(n^2);是一種就地的穩定的排序;
(2)快速排序
實現過程:將第一個值作爲基準,設置i,j指針交替從兩頭與基準比較,有交換後,交換j,i。i=j時確定基準,並以其爲界限將序列分爲兩段。重複以上步驟。
關鍵字比較次數最好爲nlog2n+nC(1)、最壞爲n(n-1)/2;
算法的最好時間是O(nlog2n);最壞時間是O(n^2);平均時間是O(nlog2n);輔助空間爲O(log2n);是一種不穩定排序;
10.選擇排序
(1)直接選擇排序
實現過程:選擇序列中最小的插入第一位,在剩餘的序列中重複上一步,共重複n-1次。
關鍵字比較次數爲n(n-1)/2;記錄移動次數最小爲0,最大爲3(n-1);
算法的最好時間是O(n^2);最壞時間是O(n^2);平均時間是O(n^2);是一種就地的不穩定的排序;
(2)堆排序
實現過程:把序列按層次填入完全二叉樹,調整位置使雙親大於或小於孩子,建立初始大根或小根堆,調整樹根與最後一個葉子的位置,排除該葉子重新調整位置。
算法的最好時間是O(nlog2n);最壞時間是O(nlog2n);平均時間是O(nlog2n);是一種就地的不穩定排序;
11.歸併排序
實現過程:將初始序列分爲2個一組,最後單數輪空,對每一組排序後作爲一個單元,對2個單元排序,直到結束。
算法的最好時間是O(nlog2n);最壞時間是O(nlog2n);平均時間是O(nlog2n);輔助空間爲O(n);是一種穩定排序;
12.分配排序
(1)箱排序
實現過程:按關鍵字的取值範圍確定箱子的個數,將序列按關鍵字放入箱中,輸出非空箱的關鍵字。
在桶內分配和收集,及對各桶進行插入排序的時間爲O(n),算法的期望時間是O(n),最壞時間是O(n^2)。
(2)基數排序
實現過程:按基數設置箱子,對關鍵字從低位到高位依次進行箱排序。
算法的最好時間是O(d*n+d*rd);最壞時間是O(d*n+d*rd);平均時間是O(d*n+d*rd);輔助空間O(n+rd);是一種穩定排序;
13.各種內部排序方法的比較和選擇:
(1)按平均時間複雜度分爲:
1) 平方階排序:直接插入、直接選擇、冒泡排序;
2) 線性對數階:快速排序、堆排序、歸併排序;
3) 指數階:希爾排序;
4) 線性階:箱排序、基數排序。
(2)選擇合適排序方法的因素:
1)待排序的記錄數;2)記錄的大小;3)關鍵字的結構和初始狀態;4)對穩定性的要求;
5)語言工具的條件;6)存儲結構; 7)時間和輔助空間複雜度。
(3)結論:
1) 若規模較小可採用直接插入或直接選擇排序;
2) 若文件初始狀態基本有序可採用直接插入、冒泡或隨機快速排序;
3) 若規模較大可採用快速排序、堆排序或歸併排序;
4) 任何藉助於比較的排序,至少需要O(nlog2n)的時間,箱排序和基數排序只適用於有明顯結構特徵的關鍵字;
5) 有的語言沒有提供指針及遞歸,使歸併、快速、基數排序算法複雜;
6) 記錄規模較大時爲避免大量移動記錄可用鏈表作爲存儲結構,如插入、歸併、基數排序,但快速、堆排序在鏈表上難以實現,可提取關鍵字建立索引表,然後對索引表排序。
第 九 章 查 找
1.查找的同時對錶做修改操作(如插入或刪除)則相應的表稱之爲動態查找表,否則稱之爲靜態查找表。
2.衡量一個查找算法次序優劣的標準是在查找過程中對關鍵字需要執行的平均比較次數(即平均查找長度ASL).
3.線性表上進行查找的方法主要有三種:順序查找、二分查找和分塊查找。
(1)順序查找的算法基本思想:是從表的一端開始順序掃描線性表,依次將掃描到的結點關鍵字與給定值K比較,若當前掃描到的結點關鍵字與k相等則查找成功;若掃描結束後,仍未找到關鍵字等於K的結點,則查找失敗。
1)順序查找方法可用鏈式存儲結構和順序存儲結構實現。
2)在順序存儲結構的順序查找算法中所設的哨兵是爲了簡化循環的邊界條件而引入的附加結點(元素),其作用是使for循環中省去判定防止下標越界的條件從而節省了比較的時間。
3)在等概率情況下,查找成功時其平均查找長度約爲表長的一半(n+1)/2.查找失敗的話其平均查找長度爲n+1.
(2)二分查找(又稱折半查找),它的算法思想:是對一有序表中的元素,從初始的查找區間開始,每經過一次與當前查找區間的中點位置上的結點關鍵字進行比較,若相等,則查找成功,否則,當前查找區間的縮小一半,按k值大小在某半個區間內重複相同的步驟進行查找,直到查找成功或失敗爲止。
1)二分查找在等概率的情況下查找成功的平均查找長度ASL爲lg(n+1)-1,在查找失敗時所需比較的關鍵字個數不超過判定樹的深度,最壞情況下查找成功的比較次數也不超過判定樹的深度┌lg(n+1)┐(不小於lg(n+1)的最小整數)
2)二分查找只適用於順序存儲結構而不能用鏈式存儲結構實現。因爲鏈表無法進行隨機訪問,如果要訪問鏈表的中間結點,就必須先從頭結點開始進行依次訪問,這就要浪費很多時間,還不如進行順序查找,而且,用鏈存儲結構將無法判定二分的過程是否結束,因此無法用鏈表實現二分查找。
(3)分塊查找(又稱索引順序查找)的基本思想:是將原表分成若干塊,各塊內部不一定有序,但表中的塊是"分塊有序"的,並抽取各塊中的最大關鍵字及其起始位置建立索引表。因爲索引表是有序的,分塊查找就是先用二分查找或順序查找確定待查結點在哪一塊,然後在已確定的塊中進行順序查找(不能用二分查找,因爲塊內是無序的)。分塊查找實際上是兩次查找過程,它的算法效率介與順序查找和二分查找之間。
4.以上三種查找方法的比較如下表:
查找算法 存儲結構 優點 缺點 適用於
順序查找 順序結構
鏈表結構 算法簡單且對錶的結構無任何要求 查找效率低 n較小的表的查找和查找較少但改動較多的表(用鏈表作存儲結構)
二分查找 順序結構 查找效率高 關鍵字要有序且只能用順序存儲結構實現 特別適用於一經建立就很少改動又經常需要查找的線性表
分塊查找 順序結構
鏈表 在表中插入或刪除記錄時就只要在該記錄所屬塊內操作,因爲塊內記錄的存放是隨意的,所以插入和刪除比較容易 要增加一個輔助數組的存儲空間,並要進行將初始表分塊排序運算 適用於有分塊特點的記錄,如一個學校的學生登記表可按系號或班號分塊。
5.樹的查找:以樹做爲表的組織形式有一個好處,就是可以實現對動態查找表進行高效率的查找。這裏講到了二叉排序樹和B-樹,以及在這些樹表上進行查找和修改操作的方法。
6.二叉排序樹(BST)又稱二叉查找樹,其定義是:二叉排序樹要或者是空樹或者滿足如下性質的二叉樹:
1)若它的左子樹非空,則左子樹上所有結點的值均小於根結點的值;
2)若它的右子樹非空,則右子樹上所有結點的值均大於根結點的值;
3)左、右子樹本身又是一棵二叉排序樹。
(1)二叉排序樹實際上是滿足BST性質的二叉樹。
(2)二叉排序樹的插入、建立的算法平均時間性能是O(nlgn),但其執行時間約爲堆排序的2至3倍。二叉排序樹的刪除操作可分三種情況進行處理:
1)*P是葉子,則直接刪除*P,即將*P的雙親*parent 中指向*P的指針域置空即可。
2)*P只有一個孩子*child,此時只需將*child和*p的雙親直接連接就可刪去*p.
3)*p有兩個孩子,則將操作轉換成刪除*p結點的中序後繼,在刪去它之前把這個結點的數據複製到原來要刪的結點位置上就完成了刪除。
(3)二叉排序樹上的查找和二分查找類似,它的關鍵字比較次數不超過樹的深度。在最好的情況下,二叉排序樹在生成的過程中比較勻稱,此時的叉排序樹是平衡的二叉樹(也就是樹中任一結點的左右子樹的高度大致相同),它的高度約爲1.44lgn,完全平衡的二叉樹高度約爲lgn.在最壞的情況下,輸入的實例產生的二叉排序樹的高度將達到O(n),這種情況應當避免。
7.關於B-樹(多路平衡查找樹)。它適合在磁盤等直接存取設備上組織動態的查找表,是一種外查找算法。
B樹的階是指B-樹的度數,B-樹的結點具有k個孩子時,該結點必有k-1(k>=2)個關鍵字。
實際上B-樹是二叉排序樹的推廣,它就是一棵m叉樹,且滿足四個性質,這些性質與二叉排序樹有相似之處,請仔細理解之。
8.上面的幾種查找方法均是建立在比較關鍵字的基礎上,因此它們的平均和最壞情況下所需的比較次數的下界是lgn+O(1).
9.散列技術:可以無需任何比較就找到待查關鍵字,其查找的期望時間爲O(1).
散列表的概念:就是將所有可能出現的關鍵字的集合U(全集)映射到一個表T[0..m-1]的下標集上,這個表就是散列表。
10.而關鍵字與這個表地址之間以什麼樣的關係發生聯繫呢,這就要通過一個函數來建立,這個函數是以U中的關鍵字爲自變量,以相應結點的存儲地址爲函數值,它就稱爲散列函數。將結點按其關鍵字的散列地址存儲到散列表的過程稱爲散列。
11.根據某種散列函數,一個關鍵字的散列函數值是唯一的,但是有可能兩個或多個不同關鍵字的函數值是相同的,這時就會把幾個結點存儲到同一個表位置上,這時就造成衝突(或碰撞)現象,這兩個關鍵字稱爲該散列函數的同義詞。
要完全(不是"安全")避免衝突需滿足兩個條件,一是關鍵字集合U不大於散列表長m,另一個是選擇合適的散列函數,如果用h(ki)=0)這樣的函數的話,看看有什麼結果。
12.通常情況下U總是大大於m的,因此不可能完全避免衝突。衝突的頻繁程度還與表的填滿程度相關。裝填因子α表示表中填入的結點數與表長的比值,通常取α≤1,因爲α越大,表越滿,衝突的機會也越大。
13.散列函數的選擇有兩條標準:簡單和均勻。看看h(ki)=0這樣的函數,簡單是簡單,但絕不均勻。
14.下面是常見的幾種散列函數構的造方法:
(1)平方取中法
(2)除餘法:它是用表長m來除關鍵字,取餘數作爲散列地址。若選除數m是關鍵字的基數的冪次,就會使得高位不同而低位相同的關鍵字互爲同義詞。因此最好選取素數爲除數.
(3)相乘取整法:有兩個步驟,先用關鍵字key乘上某個常數A(0)
(4)隨機數法,此法以關鍵字爲自變量,通過一隨機函數得到的值作爲散列地址。
15.處理衝突的方法:當不可避免發生衝突時,就必須對衝突加以解決,使發生衝突的同義詞能存儲到表中。
16.通常有兩類方法處理衝突:開放定址法和拉鍊法。前者是將所有結點均存放在散列T[0..m-1]中,後者是將互爲同義詞的結點鏈成一個單鏈表,而將此鏈表的頭指針放在散列表中。
17.開放定址法的一般形式爲:hi=(h(key)+di)%m 1≤i≤m-1
18.開放定址法要求散列表的裝填因子α≤1。開放定址法又有線性探查法、二次探查法和雙重散列法之分。
(1)由於線性探查法在構造散列表時,遇到衝突(有同義詞)的時候會按探查序列向後面的空地址插入,從而使原來應插入到此位置的結點又與它發生衝突,當一連串的位置均已有結點時,本應插入到這些位置的結點又只能將其插入到更後面的同一個空結點上,這種散列地址不同的結點爭奪同一個後繼散列地址的現象就是聚集或堆積。(注意,同義詞發生衝突不是堆積)
爲了減小堆積現象的發生,可以用二次探查法和雙重散列法進行探查。
(2)拉鍊法解決衝突的做法是,將所有關鍵字爲同義詞的結點鏈接在同一個單鏈表中。
19.與開放定址法相比,拉鍊法有如下幾個優點:
(1)拉鍊法處理衝突簡單,且無堆積現象,即非同義詞決不會發生衝突,因此平均查找長度較短;(簡單無堆積)
(2)由於拉鍊法中各鏈表上的結點空間是動態申請的,故它更適於造表前無法確定表長的情況;(動態申表長)
(3)開放定址法爲減少衝突要求裝填因子α較小,當結點規模較大時會浪費很多空間,拉鍊法中α可以大於1,且結點較大時,其指針域可忽略不計,因此節省空間;(空間可節省)
(4)拉鍊法構造的散列表刪除結點易實現,而開放定址法中則不能真正刪除結點只能做刪除標記。(刪除易實現)
20.拉鍊法也有缺點:當結點規模較小時,用拉鍊法中的指針域也要佔用額外空間,還是開放定址法省空間。
21.在散列表上的運算有查找、插入和刪除,主要是查找。這三個操作的算法並不複雜,也容易理解。關於查找操作的時間性能,可看教材p202的表9.1。由表可見,散列表的平均查找長度不是結點個數n的函數,而是裝填因子α的函數。α越小,衝突的概率越小,但空間的浪費將增加,當α大小合適時,散列表上的平均查找長度就是一個常數,時間性能是O(1).
第 十 章 文 件
1. 對數據結構來說,文件是性質相同的記錄的集合。
2.
2.記錄是文件中存取的基本單位,數據項是文件可使用的最小單位,數據項有時稱字段或者屬性。主關鍵字項(唯一標識一個記錄的字段)、次關鍵字項、主關鍵字、次關鍵字。單關鍵字文件、多關鍵字文件等。
3.文件的邏輯結構是一種線性結構。
4.文件上的操作主要有兩類:檢索和維護。並有實時和批量處理兩種處理方式。
5.文件的存儲結構是指文件在外存上的組織方式,基本的組織方式有:順序組織、索引組織、散列組織和鏈組織。文件組織的各種方式往往是這四種基本方式的結合。
6.常用的文件組織方式:順序文件、索引文件、散列文件和多關鍵字文件。
7.評價一個文件組織的效率,是執行文件操作所花費的時間和文件組織所需的存儲空間。通常文件組織的主要目的,是爲了能高效、方便地對文件進行操作,而檢索功能的多寡和速度的快慢,是衡量文件操作質量的重要標誌。
8.順序文件:是指按記錄進入文件的先後順序存放、其邏輯順序和物理順序一致的文件。
1)一切存儲在順序存儲器(如磁帶)上的文件都只能順序文件。這種順序文件只能按順序查找法存取(注意,沒有折半法了)
2)存儲在直接存取存儲器(如磁盤)上的順序文件可以順序查找法存取,也可以用分塊查找法或二分查找法存取。
3)順序文件多用於磁帶。
9.索引文件:組織方式:通常是在文件本身(主文件)之外,另外建立一張表,它指明邏輯記錄和物理記錄之間一一對應的關係,這張表就叫做索引表,它和主文件一起構成索引文件。
1)索引非順序文件中的索引表爲稠密索引。索引順序文件中的索引表爲稀疏索引。
2)若記錄很大使得索引表也很大時,可對索引表再建立索引,稱爲查找表。通常可達四級索引。
10.索引順序文件:是最常用的文件組織:因爲索引順序文件的主文件也是有序的,所以它既適合於隨機存取也適合於順序存取。另一方面,索引非順序文件的索引是稠密索引,而索引順序文件的稀疏索引,佔用空間較少,因此索引順序文件是最常用的一種文件組織。
1)索引順序文件常用的有兩種:ISAM文件和VSAM文件
11.散列文件:是利用散列存儲方式組織的文件,亦稱爲直接存取文件。
1)它類似於散列表,即根據文件中關鍵字的特點,設計一個散列函數和處理衝突的方法,將記錄散列到存儲設備上。與散列表不同的是,對於文件來說,記錄通常是成組存放的,若干個記錄組成一個存儲單位,稱爲桶。對散列而言,處理衝突的方法主要採用拉鍊法。
2)散列文件的優點是:文件隨機存放,記錄不需要排序;插入刪除方便;存取速度快;不需要索引區,節省存儲空間。缺點是:不能進行順序存取,只能按關鍵字隨機存取,且詢問方式限地簡單詢問,需要重新組織文件。
12.對被查詢的次關鍵字也建立相應的索引,則這種包含有多個次關鍵字索引的文件稱爲多關鍵字文件。
1)兩種多關鍵字文件的組織方法:多重表文件和倒排表。
2)一般的文件組織中,是先找記錄,然後再找到該記錄所含的各次關鍵字;而倒排文件是先給定次關鍵字,然後查找含有該次關鍵字的各個記錄,因此稱爲倒排。
轉自:http://blog.csdn.net/nixun/archive/2005/10/06/495716.aspx
第一天 時間:9/11/2003
真想不到,第一次上課竟然會是"9.11"事件紀念日.美國竟然還是不改老毛病,伊拉克戰爭死了多少平民百姓啊?!!!在此請先爲死難者默哀3分鐘,老美如果再這樣多管閒事下去,上帝會二度懲罰美國人的啊!
能聽到周SIR講課真的挺開心的,我覺得他講課比某些高程(高級工程師)還深動[轉載時注:此處應該是 生動](當然,他的數據結構水平應該不亞於高程),爲了考中程,上學期的<算法分析>選修課我也去揍了揍熱鬧.可惜由於SARS的關係,課只上了將近一半就停了.可以說我報程序員的原因就是因爲有周SIR開導我們,聽他的課真的是一種享受,不像大學裏的某些人,水平不怎麼高還在這裏作威作福.
好了,發了這麼多勞騷,開始轉入正題了.
讀這文章的人想必都是想報考或者將考程序員的同志們吧!首先一點必須問問自己,我考程序員的目的到底是爲了什麼?如果你的答案是:"只是爲了拿一本證書".那我勸你早點GIVE UP吧!那樣學起來會很累,因爲沒有興趣.如果你想加入程序員的行列,爲將來開發打下堅實的基礎,那就試着看完這一小篇讀書筆記吧!或許會對你有所幫助.
有句話必須記住:程序員考試僅僅是爲了檢驗自己學到的而已,僅此而已!我想這便是程序員考試的最終意義所在吧!有些事情更注重過程!
數據結構
知識:
1.數據結構中對象的定義,存儲的表示及操作的實現.
2.線性:線性表、棧、隊列、數組、字符串(廣義表不考)
樹:二叉樹
集合:查找,排序
圖(不考)
能力:
分析,解決問題的能力
過程:
● 確定問題的數據。
● 確定數據間的關係。
● 確定存儲結構(順序-數組、鏈表-指針)
● 確定算法
● 編程
● 算法評價(時間和空間複雜度,主要考時間複雜度)
一、數組
1、存放於一個連續的空間
2、一維~多維數組的地址計算方式
已知data[0][0]的內存地址,且已知一個元素所佔內存空間S求data[i][j]在內存中的地址。
公式:(add+(i*12+j)*S)(假設此數組爲data[10][12])
注意:起始地址不是data[0][0]時候的情況。起始地址爲data[-3][8]和情況;
3、順序表的定義
存儲表示及相關操作
4、順序表操作中時間複雜度估計
5、字符串的定義(字符串就是線性表),存儲表示
模式匹配算法(簡單和KMP(不考))
6、特殊矩陣:存儲方法(壓縮存儲(按行,按列))
三對角:存儲於一維數組
三對角問題:已知Aij能求出在一維數組中的下標k;已知下標k求Aij。
7、稀疏矩陣:定義,存儲方式:三元組表、十字鏈表(屬於圖部分,不考)
算法
● 數組中元素的原地逆置; 對換
● 在順序表中搜索值爲X的元素;
● 在有序表中搜索值爲X的元素;(折半查找)
● 在順序表中的第i個位置插入元素X;
● 在順序表中的第i個位置刪除元素X;
● 兩個有序表的合併;算法?
線性表數據結構定義:
Typedef struct {
int data[max_size];
int len;
}linear_list;
● 模式匹配
● 字符串相加
● 求子串
● (i,j)<=>K 注意:不同矩陣所用的公式不同;
● 稀疏矩陣的轉置(兩種方式,後種爲妙)
● 和數組有關的算法
例程:求兩個長整數之和。
a=13056952168
b=87081299
數組:
a[]:1 3 0 5 6 9 5 2 1 6 8
b[]:8 7 0 8 1 2 9 9
由於以上的結構不夠直觀(一般越是直觀越容易解決) 將其改爲:
a[]:11 8 6 1 2 5 9 6 5 0 3 1 a[0]=11(位數)
b[]: 8 9 9 2 1 8 0 7 8 0 0 0 b[0]=8
c進位 0 1 1 0 0 1 1 1 1 0 0
c[]:11 7 6 4 3 3 0 4 4 2 3 1 c[0]的值(C位數)由c[max_s+1]決定!
注意:在求C前應該將C(max_s+1)位賦0.否則爲隨機數; 較小的整數高位賦0.
算法:已知a,b兩個長整數,結果:c=a+b;
總共相加次數: max_s=max(a[],b[])
程序:
for(i=1;i<=max_s;i++) {
k=a[i]+b[i]+c[i];
c[i]=k%10;
c[i+1]=k/10;
}
求c位數:
if(c[max_s+1]==0)
c[0]=max_s;
else
c[0]=max_s+1;
以下代碼是我編的(畢竟是初學者.不太簡潔大家不要見怪!):
/*兩長整數相加*/
#include<stdio.h>
#include<string.h>
#define PRIN printf("\n");
int flag=0; /*a[0]>b[0]?1:0*/
/* max(a[],b[]) {}*/
change(char da[],char db[],int a[],int b[],int c[]) {
int i;
if(a[0]>b[0]) {
for(i=1;i<=a[0];a[i]=da[a[0]-i]-'0',i++); /*a[0]-'0' so good!*/
for(i=1;i<=b[0];b[i]=db[b[0]-i]-'0',i++);
for(i=b[0]+1;i<=a[0];b[i]=0,i++);
for(i=1;i<=a[0]+1;c[i]=0,i++);
flag=1;
}
else {
for(i=1;i<=b[0];b[i]=db[b[0]-i]-'0',i++);
for(i=1;i<=a[0];a[i]=da[a[0]-i]-'0',i++);
for(i=a[0]+1;i<=b[0];a[i]=0,i++);
for(i=1;i<=b[0]+1;c[i]=0,i++);
}
}
add(int a[],int b[],int c[]) {
int i,sum;
if(flag==1) {
for(i=1;i<=a[0];i++) {
sum=a[i]+b[i]+c[i];
c[i+1]=sum/10;
c[i]=sum%10;
}
if(c[a[0]+1]==0)
c[0]=a[0];
else
c[0]=a[0]+1;
}
else {
for(i=1;i<=b[0];i++) {
sum=a[i]+b[i]+c[i];
c[i+1]=sum/10;
c[i]=sum%10;
}
if(c[b[0]+1]==0)
c[0]=b[0];
else
c[0]=b[0]+1;
}
}
void print(int m[]) {
int i;
for(i=m[0];i>=1;i--)
printf("%d,",m[i]); PRIN
}
main(){
int s;
int a[20],b[20],c[20];
char da[]={"123456789"};
char db[]={"12344443"};
a[0]=strlen(da);
b[0]=strlen(db);
printf("a[0]=%d\t",a[0]);
printf("b[0]=%d",b[0]); PRIN
change(da,db,a,b,c);
printf("flag=%d\n",flag); PRIN
printf("-----------------\n");
if(flag==1) {
print(a); PRIN
s=abs(a[0]-b[0]);
printf("+");
for(s=s*2-1;s>0;s--)
printf(" ");
print(b); PRIN
}
else {
s=abs(a[0]-b[0]);
printf("+");
for(s=s*2-1;s>0;s--)
printf(" ");
print(a); PRIN
print(b); PRIN
}
add(a,b,c);
printf("-----------------\n");
print(c);
}
時間複雜度計算:
● 確定基本操作
● 計算基本操作次數
● 選擇T(n)
● lim(F(n)/T(n))=c
● 0(T(n))爲時間複雜度
上例子的時間複雜度爲O(max_s);
二:鏈表
1、知識點
●邏輯次序與物理次序不一致存儲方法;
●單鏈表的定義:術語(頭結點、頭指針等)
●注意帶頭結點的單鏈表與不帶頭結點的單鏈表區別。(程序員考試一般不考帶頭結點,因爲稍難理解)
●插入、刪除、遍歷(p==NULL表明操作完成)等操作
● 循環鏈表:定義,存儲表示,操作;
● 雙向鏈表:定義,存儲方法,操作;
單鏈表和循環鏈表區別在最後一個指針域值不同。
2、操作
●單鏈表:插入X,刪除X,查找X,計算結點個數
●單鏈表的逆置(中程曾考)
head->NULL/p->a1/p->a2/p->a3/p……an/NULL 注:p代表指針;NULL/p代表頭結點
=》 head->NULL/p->an/p->an-1/p->an-2/p……a1/NULL
●循環鏈表的操作:插入X,刪除X,查找X,計算結點個數;
用p=head->next來判斷一次計算結點個數完成;
程序段如下:
k=0;
do{
k++;
p=p->next;
}while(p!=head->next);
● 雙向鏈表
●多項式相加
● 有序鏈表合併
例程:已知兩個字符串S,T,求S和T的最長公子串;
1、邏輯結構:字符串
2、存儲結構:數組
3、算法: 精化(精細化工)**老頑童注:此處“精細化工”說明好像不對!
s="abaabcacb"
t="abdcabcaabcda"
當循環到s.len-1時,有兩種情況:s="abaabcacb"、s="abaabcacb"
s.len-2時,有三種情況:s="abaabcacb"、s="abaabcacb"、s="abaabcacb"
.
.
.
1 s.len種情況
程序思路:
tag=0 //沒有找到
for(l=s.len;l>0&&!tag;l--) {
判斷長度爲l的s中的子串是否爲t的子串;
若是:tag=1;
}
長度爲l的s的子串在s中有(s.len-l+1)個。
子串0: 0~l-1
1: 1~l
2: 2~l+1
3: 3~l+2
……
……
s.len-l: s.len-l~s.len-1
由上面可得:第j個子串爲j~l+j-1。
判斷長度爲l的s中的子串是否爲t的子串:
for(j=0;j<s.len-l+1&&!tag;j++){
判斷s中長度爲l的第j個子串是否爲t的子串;
如果是:tag=1;
}
模式結構:
tag=0;
for(l=s.len;l>0&&tag==0;l--) {
for(j=0;j<s.len-l+1&&!tag;j++) {
?? 用模式匹配方法確定s[j]~s[l+j-1]這個字符串是否爲t的子串; //好好想想
若是,tag=1;
}
}
第二天 時間:9/18/2003
轉眼又過了一週了,前面一週裏面我編了一些程序:鏈表,長整型數相加,三元組錶轉置以及一些簡單的函數.其實有些算法想想是很簡單,不過寫起來還是需要一定耐心和C基礎的,如果你自己覺得各算法都很懂了,不妨開機編編試試.或許會有一些新的發現與體會.
棧和隊列
1、知識點:
● 棧的定義:操作受限的線性表
● 特點:後進先出
● 棧的存儲結構:順序,鏈接
/ push(s,d)
● 棧的基本操作:
\ pop(s)
棧定義:
struct {
datatype data[max_num];
int top;
};
●隊列定義
特點:先進先出
/入隊列 in_queue(Q,x)
●隊列的操作:
\出隊列 del_queue(Q)
●隊列存儲結構:
鏈隊列:
Typedef struct node{
Datatype data;
Struct node *next;
}NODE;
Typedef struct {
NODE *front;
NODE *rear;
}Queue;
順序隊列:
struct {
datatype data[max_num];
int front,rear;
};
問題:
隊列ó線性表
假溢出<=循環隊列
隊列滿,隊列空條件一樣<=浪費一個存儲空間
遞歸
定義:問題規模爲N的解依賴於小規模問題的解。問題的求解通過小規模問題的解得到。
包括二個步驟:
1) 遞推 6!=>5!=>4!=>3!=>2!=>1!=>0!
2) 迴歸 720<=120<=24<=6 <=2 <=1 <=0
遞歸工作棧實現遞歸的機制。
2、有關算法:
1) 順序,鏈表結構下的出棧,入棧
2) 循環,隊列的入隊列,出隊列。
3) 鏈隊列的入隊列,出隊列。
4) 表達式計算:後綴表達式 35+6/4368/+*-
中綴表達式 (3+5)/6-4*(3+6/8)
由於中綴比較難處理,計算機內一般先將中綴轉換爲後綴。
運算:碰到操作數,不運算,碰到操符,運算其前兩個操作數。
中綴=>後綴
5) 迷宮問題
6) 線性鏈表的遞歸算法 一個鏈表=一個結點+一個鏈表
int fuction(NODE *p) {
if(p==NULL) return 0;
else return(function(p->next));
}
樹與二叉樹
一、 知識點:
1. 樹的定義: data_struct(D,R);
其中:D中有一個根,把D和出度去掉,可以分成M個部分.
D1,D2,D3,D4,D5…DM
R1,R2,R3,R4,R5…RM
而子樹Ri形成樹.
1) 遞歸定義 高度
2) 結點個數=1 |
此樹的高度爲2 |
2.二叉樹定義:
結點個數>=0 .
3. 術語:左右孩子,雙親,子樹,度,高度等概念.
4. 二叉樹的性質
●層次爲I的二叉樹 I層結點 2I 個
●高度爲H的二叉樹結點 2H+1-1個
●H(點)=E(邊)+1
●個數爲N的完全二叉樹高度爲|_LOG2n_|
●完全二叉樹結點編號:從上到下,從左到右.
i結點的雙親: | |_i/2_| | |_i-1/2_| | 1 | ||||||
i結點的左孩子: | 2i | 2i+1 | 2 | 3 | |||||
i結點的右孩子: | 2i+1 | 2i+2 | 4 | 5 | 6 | 7 | |||
(根) | 1爲起點 | 0爲起點 |
二叉樹的存儲結構:
1) 擴展成爲完全二叉樹,以一維數組存儲。
A | |||||||||
B | C | ||||||||
D | E | F | |||||||
G | H | I |
數組下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
元素 | A | B | C | D | E | F | G | H | I |
2) 雙親表示法
數組下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
元素 | A | B | C | D | E | F | G | H | I |
雙親 | -1 | 0 | 0 | 1 | 2 | 2 | 3 | 3 | 4 |
3) 雙親孩子表示法
數組下標 | 0 | 1 | 2 | 3 | 4 | 5 | … |
元素 | A | B | C | D | E | F | … |
雙親 | -1 | 0 | 0 | 1 | 2 | 2 | … |
左子 | 1 | 3 | 4 | … | |||
右子 | 2 | -1 | 5 | … |
結構:
typedef struct {
datatype data;
int parent;
int lchild;
int rchild;
}NODE;
NODE tree[N]; // 生成N個結點的樹
4) 二叉鏈表
5) 三叉鏈表
6) 哈夫曼樹
5.二叉樹的遍歷
先根 \
中根 棧 中根遍歷(左子樹)根(右子樹),再用相同的方法處理左子樹,右子樹.
後根 /
先,中序已知求樹:先序找根,中序找確定左右子樹.
層次遍歷(隊列實現)
6.線索二叉樹(穿線樹)
中序線索二樹樹目的:利用空指針直接得到中序遍歷的結果.
手段(方法):左指針爲空,指向前趨,右指針爲空,指向後繼.
結點結構:
ltag | Lch | Data | rch | rtag |
Ltag=0,lch指向左孩子,ltag=1,指向前趨結點
Rtag=0,rch指向右孩子;rtag=1,指向後繼結點
中序遍歷: 1) 找最左結點(其左指針爲空)
2) 當該結點的rtag=1,該結點的rch指向的就爲後繼
3) 當rtag=0,後繼元素爲右子樹中最左邊那個
N個結點的二樹有空指針N+1個
週六我去了周SIR的辦公室,他很熱情,我問的是中序線索化二叉樹的問題(遞歸),關於這個問題我會在以後的筆記中作重點補充。我在這學校從來沒有碰到過像他這樣熱情的老師,真的,大一的時候我們學校就開了C,當時我就連#include<stdio.h>這句話的意思都不曉得,別說是讓我寫程序了(到這份上也不怕把醜事都抖出來了:《數據結構》的課程設計也是哈科大的littlebob兄幫我做的,很遺憾,他高程就差幾分,希望他早日成功,我們都要向他學習)等於說我的C知識九成都是在大二下學期的時候學的。而且全是自學的。拿這個週末來說吧。我三天時間就看完了一本C語言大全。當然,並不是從頭到尾,只是根據自己的實際情況,重點是指針和數據結構那塊。C最要的便是指針。程序員考試下午試題最重要的便是遞歸問題(1~2道,沒有掌握就沒希望了哦)。我說這些並不是爲了表明自己多麼用功,只是希望每位"學者"都有所側重。
第三天 時間:9/23/2003
排序查找是我自己覺得最頭疼的算法了,常搞混去的啊.不知道各位學得如何,如果不錯,還請告訴我一些經驗!
查找
一、 知識點 /靜態查找->數組
1、 什麼是查找
\動態查找->鏈樹
●順序查找,時間複雜度 O(n)
●折半查找:條件:有序;時間複雜度 O(nlog2n) (時間複雜度實際上是查找樹的高度)
●索引查找:條件:第I+1塊的所有元素都大於第I塊的所有元素。
算法:根據index來確定X所在的塊(i) 時間複雜度:m/2
在第I塊裏順序查找X 時間複雜度:n/2
總的時間複雜度:(m+n)/2
●二叉排序樹 1)定義:左子樹鍵值大於根節點鍵值;右子樹鍵值小於根的鍵值,其左右子樹均爲二叉排序樹。
2)特點:中序遍歷有序->(刪除節點用到此性質)
3)二叉排序樹的查找:如果根大於要查找的樹,則前左子樹前進,如果根小於要查找的樹,則向右子樹前進。
4)結點的插入->二叉排序樹的構造方法
5)結點刪除(難點) 1、右子樹放在左子樹的最右邊
2、左子樹放在右子樹的最左邊
●avl樹(二叉平衡樹):左右子樹高度只能差1層,即|h|<=1其子樹也一樣。
●B樹:n階B樹滿足以下條件 1)每個結點(除根外)包含有N~2N個關鏈字。 2)所有葉子節點都在同一層。
3)B樹的所有子樹也是一棵B樹。
特點:降低層次數,減少比較次數。
排序
一、知識點
1、排序的定義
/內排序:只在內存中進行
2、排序的分類
\外排序:與內外存進行排序
內排序: /直接插入排序
1)插入排序
\shell排序
/冒泡排序
2)交換排序
\快速排序
/簡單選擇排序
3)選擇排序 堆
\ 錦標賽排序
4)歸併排序(二路)
5)基數排序(多關鏈字排序)
3、時間複雜度(上午題目常考,不會求也得記住啊兄弟姐妹們!)
* * * * * * 15 * * * 15 * * *
/穩定 * * * * * * * * 15 15 * * * *(前後不變)
排序
\ 不穩定 * * * * * * * * 15 15 * * * *(前後改變)
經整理得:選擇、希爾、堆、快速排序是不穩定的;直接插入、冒泡、合併排序是穩定的。
●錦標賽排序方法: 13 16 11 18 21 3 17 6
\ / \ / \ / \ /
13 11 3 6
\ / \ /
11 3
\ /
3(勝出,將其拿出,並令其爲正無窮&Go On)
●歸併排序方法: 13 16 11 18 21 3 17 6
\ / \ / \ / \ /
13,16 11,18 3,21 6,17
\ / \ /
11,13,16,18 3,6,17,21
\ /
3,6,11,13,16,17,18,21
●shell排序算法:1)定義一個步長(或者說增量)數組D[m];其中:D[m-1]=1(最後一個增量必須爲1,否則可能不完全)
2)共排m趟,其中第i趟增量爲D[i],把整個序列分成D[i]個子序列,分別對這D[i]個子序列進行直接插入排序。
程序如下: for(i=0;i<m;i++)
{for(j=0;j<d[i];j++)
{對第i個子序列進行直接插入排序;
注意:下標之差爲D[i];
}
}
●快速排序 ( smaller )data ( bigger )
d[] i-> 13 16 11 18 21 3 17 6 24 8 <-j
先從後往前找,再從前往後找。
思想:空一個插入一個,i空j挪,j空i挪(這裏的i,j是指i,j兩指針所指的下標)。
一次執行算法:1)令temp=d[0](樞紐),i=0,j=n-1;
2)奇數次時從j位置出發向前找第一個比temp小的元素,找到後放到i的位置(d[i]=d[j];i++;) i往後挪。
3)偶數次時從i開始往後找第一個比temp大的數,(d[j]=d[i];j--;)
4)當i=j時,結束循環。d[i]=temp;
再用遞歸對左右進行快速排序,因爲快速排序是一個典型的遞歸算法。
●堆排序
定義:d[n]滿足條件:d[i]<d[2i+1]&&d[i]<d[2i+2] 大堆(上大下小)
d[i]>d[2i+1]&&d[i]>d[2i+2] 小堆(上小下大)
判斷是否爲堆應該將其轉換成樹的形式。總共排序n-1次
調整(重點)
程序: flag=0;
while(i<=n-1) {
if(d[i]<d[2*i+1])||(d[i]<d[2*i+2]))
{ if(d[2*i+1]>d[2*i+2]) 8 24 {d[i]<->d[2*i+1]; 24 21 -> 8 21
i=2*i+1;
else {
d[i]<->d[2*i+2];
i=2*i+2;
}
}
else
flag=1; //是堆
}
堆排序過程:
●基數排序(多關鍵字排序)
撲克: 1) 大小->分配
2) 花色->分配,收集
思想:分配再收集.
構建鏈表:鏈表個數根據關鍵字取值個數有關.
例:將下面九個三位數排序:
321 214 665 102 874 699 210 333 600
定義一個有十個元素的數組:
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
第一趟(個位): 210 321 102 333 214 665 699
600 874
結果: 210 600 321 102 333 214 874 665 699
第二趟(十位): 600 210 321 333 665 874 699
102 214
結果: 600 102 210 214 321 333 665 874 699
第三趟(百位): 102 210 321 600 874
214 333 665
699
結果: 102 210 214 321 333 600 665 699 874(排序成功)
最近在看一位程序員的筆記,也挺不錯的啊.這應當是他的網站.他總說他的網站人氣不夠,現在順便就幫他宣傳一下啦!http://zhgpa.vicp.net/bbs,大家有時間多去去哦,呵呵!謝謝大夥支持!另外,還向大家推薦一個網站:http://kaowang.com/,挺不錯的一個考試網站。學到不少東東啊!
八大類算法
程序員考試下午試題最後一道一般是八大類算法裏頭的.大家尤其要注意的是遞歸,因爲近幾年都考了,而且有的還考兩題。可以說如果我們不掌握遞歸就沒有掌握C,況且遞歸是C裏的難點。爲了控制合格率,程序員考試不會讓我們輕鬆過關的,爲了中國軟件業,我想也應該這樣啊。
/數據結構(離散)
迭代
\數值計算(連續)
枚舉 策略好壞很重要
遞推
遞歸
回溯
分治
貪婪
動態規劃
其中:遞推、遞歸、分治、動態規劃四種算法思想基本相似。都是把大問題變成小問題,但技術上有差別。
第四天 時間:9/26/2003
枚舉:
揹包問題:
枚舉策略:1)可能的方案:2N
2)對每一方案進行判斷.
枚舉法一般流程:
while(還有其他可能方案)
{ 按某種順序可難方案;
檢驗方案;
if(方案爲解)
保存方案;
}
}
枚舉策略:
例:把所有排列枚舉出來 P6=6!.
Min:123456
Max:654321
a1a2a3a4a5a6=>?(下一排列)=>?
比如:312654的下和種情況=>314256
遞歸
遞歸算法通常具有這樣的特徵:爲求解規模爲N的問題,設法將它分解成一些規模較小的問題,然後從這些較小問題的解能方便地構造出題目所需的解。而這些規模較小的問題也採用同樣的方法分解成規模更小的問題,通過規模更小的問題構造出規模校小的問題的解,如此不斷的反複分解和綜合,總能分解到最簡單的能直接得到解的情況。
因此,在解遞歸算法的題目時,要注意以下幾點:
1) 找到遞歸調用的結束條件或繼續遞歸調用條件.
2) 想方設法將處理對象的規模縮小或元素減少.
3) 由於遞歸調用可理解爲並列同名函數的多次調用,而函數調用的原則是一層一層調用,一層一層返回.因此,還要注意理解調用返回後的下一個語句的作用.在一些簡單的遞歸算法中,往往不需要考慮遞調用返回後的語句處理.而在一些複雜的遞歸算法中,則需要考慮遞歸調用返回後的語句處理和進一步的遞歸調用.
4) 在讀遞歸程序或編寫遞歸程序時,必須要牢記遞歸函數的作用,這樣便於理解整個函數的功能和知道哪兒需要寫上遞歸調用語句.當然,在解遞歸算法的題目時,也需要分清遞歸函數中的內部變量和外部變量.
表現形式:
●定義是遞歸的(二叉樹,二叉排序樹)
●存儲結構是遞歸的(二叉樹,鏈表,數組)
●由前兩種形式得出的算法是遞歸的
一般流程: function(variable list(規模爲N))
{ if(規模小,解已知) return 解;
else {
把問題分成若干個部分;
某些部分可直接得到解;
而另一部分(規模爲N-1)的解遞歸得到;
}
}
例1:求一個鏈表裏的最大元素.
大家有沒想過這個問題用遞歸來做呢?
非遞歸方法大家應該都會哦?
Max(nodetype *h) {
nodetype *p;
nodetype *q; //存放含最大值的結點
Int max=0;
P=h;
While(p!=NULL){
if (max<p->data) {
max=p->data;
q=p;
}
p=p->next;
}
return q;
}
下面真經來了,嘻嘻嘻~~~
*max(nodetype *h) {
nodetype *temp;
temp=max(h->next);
if(h->data>temp->data)
return h;
else
return temp;
}
大家有空想想下面這個算法:求鏈表所有數據的平均值(我也沒試過),不許偷懶,用遞歸試試哦!
遞歸程序員考試題目類型:1)就是鏈表的某些操作(比如上面的求平均值)
2)二叉樹(遍歷等)
例2.判斷數組元素是否遞增
int jidge(int a[],int n) {
if(n==1) return 1;
else
if(a[0]>a[1]) return 0;
else return jidge(a+1,n-1);
}
例3.求二叉樹的高度(根據二叉樹的遞歸性質:(左子樹)根(右子樹))
int depth(nodetype *root) {
if(root==NULL)
return 0;
else {
h1=depth(root->lch);
h2=depth(root->rch);
return max(h1,h2)+1;
}
}
自己想想求二叉樹結點個數(與上例類似)
例4.已知中序遍歷和後序遍歷,求二叉樹.
設一二叉樹的:
中序 S:E D F B A G J H C I
^start1 ^j ^end1
後序 T:E F D B J H G I C A
^start2 ^end2
node *create(char *s,char *t, int start1,int start2,int end1,int end2)
{ if (start1>end1) return NULL; //迴歸條件
root=(node *)malloc(sizeof(node));
root->data=t[end2];
找到S中T[end2]的位置爲 j
root->lch=create(S,T,s1,j-1,start1,j+start2-start1-1);
root->rch=create(S,T,j+1,end1,j+start2-start1,end2-1);
return root;
}
例5.組合問題
n 個數: (1,2,3,4,…n)求從中取r個數的所有組合.
設n=5,r=3;
遞歸思想:先固定一位 5 (從另四個數當中選二個)
5,4 (從另三個數當中選一個)
5,4,3 (從另二個數當中選零個)
即:n-2個數中取r-2個數的所有組合
…
程序:
void combire(int n,int r) {
for(k=n;k>=n+r-1;k--) {
a[r]=k;
if(r==0) 打印a數組(表示找到一個解);
else combire(n-1,r-1);
}
}
第五天 9/28/2003
回溯法:
回溯跟遞歸都是程序員考試裏常出現的問題,大家必須掌握!
回溯法的有關概念:
1) 解答樹:葉子結點可能是解,對結點進行後序遍歷.
2) 搜索與回溯
五個數中任選三個的解答樹(解肯定有三層,至葉子結點):
ROOT 虛根
/ / | \ \
1 2 3 4 5
/ | | \ / | \ /\ |
2 3 4 5 3 4 5 4 5 5
/|\ /\ | /\ | |
3 4 5 4 5 5 4 5 5 5
回溯算法實現中的技巧:棧
要搞清回溯算法,先舉一個(中序遍歷二叉樹的非遞歸算法)來說明棧在非遞歸中所起的作用。
A 過程:push()->push()->push()->push()棧內結果:ABDE(E爲葉子,結束進棧)
/ \ pop() ABD(E無右孩子,出棧)
B C pop() AB(D無右孩子,出棧)
/\ pop() A(B有右孩子,右孩子進棧)
D F . .
/ /\ . .
E G H . .
/ . .
I 最後結果: EDBGFIHAC
簡單算法:
…
if(r!=NULL) //樹不空
{ while(r!=NULL)
{ push(s,r);
r=r->lch; //一直向左孩子前進
}
while(!empty(s)) // 棧非空,出棧
{ p=pop(s);
printf(p->data);
p=p->rch; //向右孩子前進
while(p!=NULL)
{ push(s,p);
p=p->lch; //右孩子進棧
}
}
} //這就是傳說中的回溯,嘻嘻……沒嚇着你吧
5選3問題算法:
思想: 進棧:搜索
出棧:回溯
邊建樹(進棧)邊遍歷(出棧)
基本流程:
太複雜了,再說我不太喜歡用WORD畫圖(有損形象),以後再整理!
程序: n=5;r=3
……
init(s) //初始化棧
push(s,1) //根進棧
while(s.top<r-1)&&(s.data[s.top]!=n) //有孩子
push(s,s.data[s.top]+1); //孩子入棧
while(!empty(s))
{ if(s.top=r-1)
判斷該"解"是否爲解.
x=pop(s); //保留x,判斷是否爲最大值n,如果是n,則出棧
while(x==n)
x=pop(s);
push(s,x+1);
while(s.top<r-1)&&(s.data[s.top]!=n)
push(s,s.data[s.top]+1);
}
揹包問題: TW=20 , w[5]={6,10,7,5,8}
解的條件:1) 該解答樹的葉子結點
2) 重量最大
解答樹如下: ROOT
/ | | | \
6 10 7 5 8
/ | | \ / | \ / \ |
10 7 5 8 7 5 8 5 8 8
| | |
5 8 8
程序:
temp_w 表示棧中重量和
…
init(s); //初始化棧
i=0;
While(w[i]>TW)
i++;
If(i==n) Return -1; //無解
Else {
Push(s,i);
Temp_w=w[i];
i++;
while(i<n)&&(temp_w+w[i]<=TW)
{ push(s,i);
temp_w+=w[i];
i++;
}
max_w=0;
while(!empty(s))
{ if(max_w<temp_w)
max_w=temp_w;
x=pop(s);
temp_w-=w[x];
x++;
while(x<n)&&(temp_w+w[x]>TW)
x++;
while(x<n)
{ push(s,x);
temp_w=temp_w+w[x];
x++;
while(x<n)&&(temp_w+w[x]>TW)
x++;
}
}
請大家思考:四色地圖問題,比如給中國地圖塗色,有四種顏色,每個省選一種顏色,相鄰的省不能取同樣的顏色.不許偷懶,不能選人口不多於xxxxW的"大國"哦!如果真的有一天台灣獨立了,下場就是這樣了,一種顏色就打發了,不過臺灣的程序員們賺到了,省事!呵呵。
貪婪法:
不求最優解,速度快(以精確度換速度)
例:哈夫曼樹,最小生成樹
裝箱問題:
有n個物品,重量分別爲w[n],要把這n個物品裝入載重爲TW的集裝箱內,需要幾個集裝箱?
思想1:對n個物品排序
拿出第1個集裝箱,從大到小判斷能不能放。
2 …
3 …
. …
. …
思想2: 對n個物品排序
用物品的重量去判斷是否需要一隻新箱子,如果物品重量小於本箱子所剩的載重量,則裝進去,反之則取一隻新箱子。
程序:
count=1;qw[0]=TW;
for(i=0;i<n;i++)
{
k=0;
while(k<count)&&(w[i]>qw[k])
k++;
if(w[i]<=qw[k])
qw[k]=qw[k]-w[i];
code[i]=k; //第i個物品放在第k個箱子內
else
{count++; //取一個新箱子
qw[count-1]=TW-w[i];
code[i]=count-1;
}
}
用貪婪法解揹包問題:
n個物品,重量:w[n] 價值v[i]
揹包限重TW,設計一個取法使得總價值最大.
方法:
0 1 2 3 … n-1
w0 w1 w2 w3 … wn-1
v0 v1 v2 v3 … vn-1
v0/w0 … v(n-1)/w(n-1) 求出各個物品的"性價比"
先按性價比從高到低進行排序
已知:w[n],v[n],TW
程序:
…
for(I=1;I<n;I++)
d[i]=v[i]/w[i]; //求性價比
for(I=0;I<n;I++)
{ max=-1;
for(j=0;j<n;j++)
{ if(d[j]>max)
{ max=d[j];x=j; }
}
e[i]=x;
d[x]=0;
}
temp_w=0;temp_v=0;
for(i=0;i<n;i++)
{ if(temp_w+w[e[i]]<=TW)
temp_v=temp_v+v[e[v]];
}
分治法:
思想:把規模爲n的問題進行分解,分解成幾個小規模的問題.然後在得到小規模問題的解的基礎上,通過某種方法組合成該問題的解.
例:數軸上有n個點x[n],求距離最小的兩個點.
分:任取一點,可以把x[i]這n個點分成兩個部分
小的部分 分點 大的部分
|_._.__.__.____._|__._._.__._.__._______._.__._._.__.___._____._|
治:解=min{小的部分的距離最小值;
大的部分的距離最小值;
大的部分最小點和小的部分最大點這兩點之差;}
第六天(想起了兒時的《最後一課》^_^) 10/7/2003
快考試了,老師沒有多說什麼,他只是給我們講了講近三年試題情況,並詳細講述了兩道題目:一道遞歸,另一道回溯。不過今天不知怎麼回事,我感覺特別累,也特別想睡覺。所以沒什麼感覺。這裏就不說了,兩道題目都有的,遞歸那題是2001年最後一題,回溯是在《程序員教程》P416,老師說高程曾經考過,有興趣自己看看也能看懂的。老師特意爲我們出了一份下午試題,我現在把他拿出來讓大家參考參考。到這裏,就到這裏!
程序員考試下午試題(模擬)
一、把一個字符串插入到另一個字符串的某個位置(指元素個數)之後
char *insert(char *s,char *t,int position)
{ int i;
char *target;
if(position>strlen(t)) printf("error");
else
{ for (i=0;i< (1) ;i++)
{ if (i<position)
target[i]=s[i];
else
{ if(i< (2) )
target[i]=t[i];
else (3) ;
}
}
}
return garget;
}
二、輾轉相除法求兩個正整數的最大公約數
int f(int a,int b)
{ if (a==b) (4) ;
else
{ if (a>b) return f(a-b,b);
else (5) ;
}
}
三、求一個鏈表的所有元素的平均值
typedef struct { int num;
float ave;
}Back;
typedef struct node{ float data;
struct node *next;
} Node;
Back *aveage(Node *head)
{ Back *p,*q;
p=(Back *)malloc(sizeof(Back));
if (head==NULL)
{ p->num=0;
p->ave=0; }
else
{ (6) ;
p->num=q->num+1;
(7) ; }
retuen p;
}
main()
{ Node *h; Back *p;
h=create(); /*建立以h爲頭指針的鏈表*/
if (h==NULL) printf("沒有元素");
else { p=aveage(h);
printf("鏈表元素的均值爲:%6f",p->ave);
}
}
四、希爾排序
已知待排序序列data[n];希爾排序的增量序列爲d[m],其中d[]序列降序排列,且d[m-1]=1。其方法是對序列進行m趟排序,在第i趟排序中,按增量d[i]把整個序列分成d[i]個子序列,並按直接插入排序的方法對每個子序列進行排序。
希爾排序的程序爲:
void shellsort(int *data,int *d,int n,int m)
{ int i,j;
for (i=0;i<m;i++)
for (j=0; (1) ;j++)
shell( (2) );
}
void shell(int *data,int d,int num,int n)
{ int i,j,k,temp;
for (i=1; (3) ;i++)
{ j=0;
temp=data[j+i*d];
while ((j<i)&&( (4) ))
j++;
for (k=j;k<i;k++)
data[k+1]=data[k];
(5) ;
(6) }
}
五、求樹的寬度
所謂寬度是指在二叉樹的各層上,具有結點數最多的那一層上的結點總數。本算法是按層次遍歷二叉樹,採用一個隊列q,讓根結點入隊列,最後出隊列,若有左右子樹,則左右子樹根結點入隊列,如此反覆,直到隊列爲空。
int Width(BinTree *T)
{ int front=-1,rear=-1; /* 隊列初始化*/
int flag=0,count=0,p;/*p用於指向樹中層的最右邊的結點,flag記錄層中結點數的最大值。*/
if(T!=Null)
{ rear++; (1) ; flag=1; p=rear;
}
while( (2) )
{ front++;
T=q[front];
if(T->lchild!=Null)
{ rear++; (3) ; count++; } //
if(T->rchild!=Null)
{ rear++; q[rear]=T->rchild; (4) ; }
if(front==p) /* 當前層已遍歷完畢*/
{ if( (5) ) flag=count; count=0; //
p=rear; /* p指向下一層最右邊的結點*/
}
}
return(flag);
}
六、區間覆蓋
設在實數軸上有n個點(x0,x1,……,xn-2,xn-1),現在要求用長度爲1的單位閉區間去覆蓋這n個點,則需要多少個單位閉區間。
int cover(float x[ ], int num)
{ float start[num],end[num];
int i ,j ,flag, count=0;
for (i=0;i<num;i++)
{ flag=1;
for (j=0;j< (1) ;j++)
{ if ((start[j]>x[i])&&(end[j]-x[i]<=1)) (2) ;
else if ( (3) ) end[j]=x[i];
else if ((x[i]>start[j])&&(x[i]<end[j])) flag=0;
if (flag) break;
}
if ( (4) )
{ end[count]=x[i]; (5); count++; }
}
return count-1;
}
start[count]=x[i]
七、圍棋中的提子
在圍棋比賽中,某一方(假設爲黑方)在棋盤的某個位置(i,j)下子後,有可能提取對方(白方的一串子)。以W[19][19]表示一個棋盤,若W[i][j]=0表示在位置(i,j)上沒有子,W[i][j]=1表示該位置上的是黑子,W[i][j]=-1表示該位置上是白子。可以用回溯法實現提子算法。
下列程序是黑棋(tag=1)下在(i,j)位置後判斷是否可以吃掉某些白子,這些確定可以提掉的白子以一個線性表表示。
問題相應的數據結構有:
#define length 19 /*棋盤大小*/
#define max_num 361 /*棋盤中點的數量*/
struct position { int row; int col;
}; /*棋子位置*/
struct killed { struct position data[max_num]; int num;
} *p; /*存儲可以吃掉的棋子位置*/
struct stack { struct position node[max_num]; int top;
}; /*棧*/
int w[length][length]; /*棋盤中雙方的棋子分佈*/
int visited[length][length]; /*給已搜索到的棋子位置作標記,初值爲0,搜索到後爲1*/
struct killed *kill(int w[length][length],int r,int c,int tag)
{ struct killed *p;
struct position *s;
struct stack S;
for (i=0;i<length;i++)
for (j=0;j<length;j++)
(1) ;
S.top=-1; p->num=-1;
if (w[r-1][c]==tag*(-1)) s->row=r-1; s->col=c;
else if (w[r+1][c]==tag*(-1)) s->row=r+1; s->col=c;
else if (w[r][c-1]==tag*(-1)) s->row=r; s->col=c-1;
else if (w[r][c+1]==tag*(-1)) s->row=r; s->col=c+1;
else p->len=0; return p;
push(S,s); visited[s->row][s->col]=1;
flag=search(s,tag);
while ( (2))
{ push(S,s); visited[s->row][s->col]=1;
(3);
}
while (S->top>=0)
{ pop(S);
(4);
flag=search(s,tag);
while (flag)
{ push(S,s);
visit(s);
flag=search(s);
}
}
}
void push( struct stack *S, struct position *s)
{ S->top++;
S->node[S->top].row=s->row;
S->node[S->top].col=s->col;
p->num++;
p->data[p->num].row=s->row;
p->data[p->num].col=s->col;
}
void pop(struct stack *S)
{ S->top--;
}
struct position *gettop(struct stack *S)
{ struct position *s;
s->row=S->data[S->top].row;
s->row=S->data[S->top].row;
return s;
}
int search(struct position *s,int tag)
{ int row,col;
row=s->row; col=s->col;
if (W[row+1][col]=(-1)*tag)&&(!visited[row+1][col])
{ s->row=row+1;s->col=col; return 1;}
if (W[row-1][col]=(-1)*tag)&&(!visited[row-1][col])
{ s->row=row-1;s->col=col; return 1;}
if (W[row][col+1]=(-1)*tag)&&(!visited[row][col+1])
{ s->row=row;s->col=col+1; return 1;}
if (W[row][col-1]=(-1)*tag)&&(!visited[row][col-1])
{ s->row=row;s->col=col-1; return 1}
(5);
}
答案:
(1)strlen(s)+strlen(t) (2)position+strlen(t) (3)target[i]=s[i-strlen(t)]
(4)return a (5)return f(a,b-a)
(6)q=aveage(head->next) (7)p->ave=(head->data+q->ave*q->num)/p->num
(1)j<d[i] (2)data,d[i],j,n (3)num+i*d<n (4)data[j+i*d]<temp (5)data[j]=temp
(1)q[rear]=T (2)front<p (3)q[rear]=T->lchild (4)count++ (5)flag<count
(1)count (2)(x[i]>end[j])&&(x[i]-start[j]<=1) (3)start[j]=x[i] (4)!flag (5)
(1)visited[i][j]=0 (2)flag (3)flag=search(s,tag) (4)s=gettop(S) (5)return 0
課已全部授完,也該收筆了.望對大家有所啓發吧!
最後祝大家順利過關
2.二叉樹定義:
結點個數>=0 .
3. 術語:左右孩子,雙親,子樹,度,高度等概念.
4. 二叉樹的性質
●層次爲I的二叉樹 I層結點 2I 個
●高度爲H的二叉樹結點 2H+1-1個
●H(點)=E(邊)+1
●個數爲N的完全二叉樹高度爲|_LOG2n_|
●完全二叉樹結點編號:從上到下,從左到右.
i結點的雙親: | |_i/2_| | |_i-1/2_| | 1 | ||||||
i結點的左孩子: | 2i | 2i+1 | 2 | 3 | |||||
i結點的右孩子: | 2i+1 | 2i+2 | 4 | 5 | 6 | 7 | |||
(根) | 1爲起點 | 0爲起點 |
二叉樹的存儲結構:
1) 擴展成爲完全二叉樹,以一維數組存儲。
A | |||||||||
B | C | ||||||||
D | E | F | |||||||
G | H | I |
數組下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
元素 | A | B | C | D | E | F | G | H | I |
2) 雙親表示法
數組下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
元素 | A | B | C | D | E | F | G | H | I |
雙親 | -1 | 0 | 0 | 1 | 2 | 2 | 3 | 3 | 4 |
3) 雙親孩子表示法
數組下標 | 0 | 1 | 2 | 3 | 4 | 5 | … |
元素 | A | B | C | D | E | F | … |
雙親 | -1 | 0 | 0 | 1 | 2 | 2 | … |
左子 | 1 | 3 | 4 | … | |||
右子 | 2 | -1 | 5 | … |
結構:
typedef struct {
datatype data;
int parent;
int lchild;
int rchild;
}NODE;
NODE tree[N]; // 生成N個結點的樹
4) 二叉鏈表
5) 三叉鏈表
6) 哈夫曼樹
5.二叉樹的遍歷
先根 \
中根 棧 中根遍歷(左子樹)根(右子樹),再用相同的方法處理左子樹,右子樹.
後根 /
先,中序已知求樹:先序找根,中序找確定左右子樹.
層次遍歷(隊列實現)
6.線索二叉樹(穿線樹)
中序線索二樹樹目的:利用空指針直接得到中序遍歷的結果.
手段(方法):左指針爲空,指向前趨,右指針爲空,指向後繼.
結點結構:
ltag | Lch | Data | rch | rtag |
Ltag=0,lch指向左孩子,ltag=1,指向前趨結點
Rtag=0,rch指向右孩子;rtag=1,指向後繼結點
中序遍歷: 1) 找最左結點(其左指針爲空)
2) 當該結點的rtag=1,該結點的rch指向的就爲後繼
3) 當rtag=0,後繼元素爲右子樹中最左邊那個
N個結點的二樹有空指針N+1個
週六我去了周SIR的辦公室,他很熱情,我問的是中序線索化二叉樹的問題(遞歸),關於這個問題我會在以後的筆記中作重點補充。我在這學校從來沒有碰到過像他這樣熱情的老師,真的,大一的時候我們學校就開了C,當時我就連#include<stdio.h>這句話的意思都不曉得,別說是讓我寫程序了(到這份上也不怕把醜事都抖出來了:《數據結構》的課程設計也是哈科大的littlebob兄幫我做的,很遺憾,他高程就差幾分,希望他早日成功,我們都要向他學習)等於說我的C知識九成都是在大二下學期的時候學的。而且全是自學的。拿這個週末來說吧。我三天時間就看完了一本C語言大全。當然,並不是從頭到尾,只是根據自己的實際情況,重點是指針和數據結構那塊。C最要的便是指針。程序員考試下午試題最重要的便是遞歸問題(1~2道,沒有掌握就沒希望了哦)。我說這些並不是爲了表明自己多麼用功,只是希望每位"學者"都有所側重。
第三天 時間:9/23/2003
排序查找是我自己覺得最頭疼的算法了,常搞混去的啊.不知道各位學得如何,如果不錯,還請告訴我一些經驗!
查找
一、 知識點 /靜態查找->數組
1、 什麼是查找
\動態查找->鏈樹
●順序查找,時間複雜度 O(n)
●折半查找:條件:有序;時間複雜度 O(nlog2n) (時間複雜度實際上是查找樹的高度)
●索引查找:條件:第I+1塊的所有元素都大於第I塊的所有元素。
算法:根據index來確定X所在的塊(i) 時間複雜度:m/2
在第I塊裏順序查找X 時間複雜度:n/2
總的時間複雜度:(m+n)/2
●二叉排序樹 1)定義:左子樹鍵值大於根節點鍵值;右子樹鍵值小於根的鍵值,其左右子樹均爲二叉排序樹。
2)特點:中序遍歷有序->(刪除節點用到此性質)
3)二叉排序樹的查找:如果根大於要查找的樹,則前左子樹前進,如果根小於要查找的樹,則向右子樹前進。
4)結點的插入->二叉排序樹的構造方法
5)結點刪除(難點) 1、右子樹放在左子樹的最右邊
2、左子樹放在右子樹的最左邊
●avl樹(二叉平衡樹):左右子樹高度只能差1層,即|h|<=1其子樹也一樣。
●B樹:n階B樹滿足以下條件 1)每個結點(除根外)包含有N~2N個關鏈字。 2)所有葉子節點都在同一層。
3)B樹的所有子樹也是一棵B樹。
特點:降低層次數,減少比較次數。
排序
一、知識點
1、排序的定義
/內排序:只在內存中進行
2、排序的分類
\外排序:與內外存進行排序
內排序: /直接插入排序
1)插入排序
\shell排序
/冒泡排序
2)交換排序
\快速排序
/簡單選擇排序
3)選擇排序 堆
\ 錦標賽排序
4)歸併排序(二路)
5)基數排序(多關鏈字排序)
3、時間複雜度(上午題目常考,不會求也得記住啊兄弟姐妹們!)
* * * * * * 15 * * * 15 * * *
/穩定 * * * * * * * * 15 15 * * * *(前後不變)
排序
\ 不穩定 * * * * * * * * 15 15 * * * *(前後改變)
經整理得:選擇、希爾、堆、快速排序是不穩定的;直接插入、冒泡、合併排序是穩定的。
●錦標賽排序方法: 13 16 11 18 21 3 17 6
\ / \ / \ / \ /
13 11 3 6
\ / \ /
11 3
\ /
3(勝出,將其拿出,並令其爲正無窮&Go On)
●歸併排序方法: 13 16 11 18 21 3 17 6
\ / \ / \ / \ /
13,16 11,18 3,21 6,17
\ / \ /
11,13,16,18 3,6,17,21
\ /
3,6,11,13,16,17,18,21
●shell排序算法:1)定義一個步長(或者說增量)數組D[m];其中:D[m-1]=1(最後一個增量必須爲1,否則可能不完全)
2)共排m趟,其中第i趟增量爲D[i],把整個序列分成D[i]個子序列,分別對這D[i]個子序列進行直接插入排序。
程序如下: for(i=0;i<m;i++)
{for(j=0;j<d[i];j++)
{對第i個子序列進行直接插入排序;
注意:下標之差爲D[i];
}
}
●快速排序 ( smaller )data ( bigger )
d[] i-> 13 16 11 18 21 3 17 6 24 8 <-j
先從後往前找,再從前往後找。
思想:空一個插入一個,i空j挪,j空i挪(這裏的i,j是指i,j兩指針所指的下標)。
一次執行算法:1)令temp=d[0](樞紐),i=0,j=n-1;
2)奇數次時從j位置出發向前找第一個比temp小的元素,找到後放到i的位置(d[i]=d[j];i++;) i往後挪。
3)偶數次時從i開始往後找第一個比temp大的數,(d[j]=d[i];j--;)
4)當i=j時,結束循環。d[i]=temp;
再用遞歸對左右進行快速排序,因爲快速排序是一個典型的遞歸算法。
●堆排序
定義:d[n]滿足條件:d[i]<d[2i+1]&&d[i]<d[2i+2] 大堆(上大下小)
d[i]>d[2i+1]&&d[i]>d[2i+2] 小堆(上小下大)
判斷是否爲堆應該將其轉換成樹的形式。總共排序n-1次
調整(重點)
程序: flag=0;
while(i<=n-1) {
if(d[i]<d[2*i+1])||(d[i]<d[2*i+2]))
{ if(d[2*i+1]>d[2*i+2]) 8 24 {d[i]<->d[2*i+1]; 24 21 -> 8 21
i=2*i+1;
else {
d[i]<->d[2*i+2];
i=2*i+2;
}
}
else
flag=1; //是堆
}
堆排序過程:
●基數排序(多關鍵字排序)
撲克: 1) 大小->分配
2) 花色->分配,收集
思想:分配再收集.
構建鏈表:鏈表個數根據關鍵字取值個數有關.
例:將下面九個三位數排序:
321 214 665 102 874 699 210 333 600
定義一個有十個元素的數組:
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
第一趟(個位): 210 321 102 333 214 665 699
600 874
結果: 210 600 321 102 333 214 874 665 699
第二趟(十位): 600 210 321 333 665 874 699
102 214
結果: 600 102 210 214 321 333 665 874 699
第三趟(百位): 102 210 321 600 874
214 333 665
699
結果: 102 210 214 321 333 600 665 699 874(排序成功)
最近在看一位程序員的筆記,也挺不錯的啊.這應當是他的網站.他總說他的網站人氣不夠,現在順便就幫他宣傳一下啦!http://zhgpa.vicp.net/bbs,大家有時間多去去哦,呵呵!謝謝大夥支持!另外,還向大家推薦一個網站:http://kaowang.com/,挺不錯的一個考試網站。學到不少東東啊!
八大類算法
程序員考試下午試題最後一道一般是八大類算法裏頭的.大家尤其要注意的是遞歸,因爲近幾年都考了,而且有的還考兩題。可以說如果我們不掌握遞歸就沒有掌握C,況且遞歸是C裏的難點。爲了控制合格率,程序員考試不會讓我們輕鬆過關的,爲了中國軟件業,我想也應該這樣啊。
/數據結構(離散)
迭代
\數值計算(連續)
枚舉 策略好壞很重要
遞推
遞歸
回溯
分治
貪婪
動態規劃
其中:遞推、遞歸、分治、動態規劃四種算法思想基本相似。都是把大問題變成小問題,但技術上有差別。
第四天 時間:9/26/2003
枚舉:
揹包問題:
枚舉策略:1)可能的方案:2N
2)對每一方案進行判斷.
枚舉法一般流程:
while(還有其他可能方案)
{ 按某種順序可難方案;
檢驗方案;
if(方案爲解)
保存方案;
}
}
枚舉策略:
例:把所有排列枚舉出來 P6=6!.
Min:123456
Max:654321
a1a2a3a4a5a6=>?(下一排列)=>?
比如:312654的下和種情況=>314256
遞歸
遞歸算法通常具有這樣的特徵:爲求解規模爲N的問題,設法將它分解成一些規模較小的問題,然後從這些較小問題的解能方便地構造出題目所需的解。而這些規模較小的問題也採用同樣的方法分解成規模更小的問題,通過規模更小的問題構造出規模校小的問題的解,如此不斷的反複分解和綜合,總能分解到最簡單的能直接得到解的情況。
因此,在解遞歸算法的題目時,要注意以下幾點:
1) 找到遞歸調用的結束條件或繼續遞歸調用條件.
2) 想方設法將處理對象的規模縮小或元素減少.
3) 由於遞歸調用可理解爲並列同名函數的多次調用,而函數調用的原則是一層一層調用,一層一層返回.因此,還要注意理解調用返回後的下一個語句的作用.在一些簡單的遞歸算法中,往往不需要考慮遞調用返回後的語句處理.而在一些複雜的遞歸算法中,則需要考慮遞歸調用返回後的語句處理和進一步的遞歸調用.
4) 在讀遞歸程序或編寫遞歸程序時,必須要牢記遞歸函數的作用,這樣便於理解整個函數的功能和知道哪兒需要寫上遞歸調用語句.當然,在解遞歸算法的題目時,也需要分清遞歸函數中的內部變量和外部變量.
表現形式:
●定義是遞歸的(二叉樹,二叉排序樹)
●存儲結構是遞歸的(二叉樹,鏈表,數組)
●由前兩種形式得出的算法是遞歸的
一般流程: function(variable list(規模爲N))
{ if(規模小,解已知) return 解;
else {
把問題分成若干個部分;
某些部分可直接得到解;
而另一部分(規模爲N-1)的解遞歸得到;
}
}
例1:求一個鏈表裏的最大元素.
大家有沒想過這個問題用遞歸來做呢?
非遞歸方法大家應該都會哦?
Max(nodetype *h) {
nodetype *p;
nodetype *q; //存放含最大值的結點
Int max=0;
P=h;
While(p!=NULL){
if (max<p->data) {
max=p->data;
q=p;
}
p=p->next;
}
return q;
}
下面真經來了,嘻嘻嘻~~~
*max(nodetype *h) {
nodetype *temp;
temp=max(h->next);
if(h->data>temp->data)
return h;
else
return temp;
}
大家有空想想下面這個算法:求鏈表所有數據的平均值(我也沒試過),不許偷懶,用遞歸試試哦!
遞歸程序員考試題目類型:1)就是鏈表的某些操作(比如上面的求平均值)
2)二叉樹(遍歷等)
例2.判斷數組元素是否遞增
int jidge(int a[],int n) {
if(n==1) return 1;
else
if(a[0]>a[1]) return 0;
else return jidge(a+1,n-1);
}
例3.求二叉樹的高度(根據二叉樹的遞歸性質:(左子樹)根(右子樹))
int depth(nodetype *root) {
if(root==NULL)
return 0;
else {
h1=depth(root->lch);
h2=depth(root->rch);
return max(h1,h2)+1;
}
}
自己想想求二叉樹結點個數(與上例類似)
例4.已知中序遍歷和後序遍歷,求二叉樹.
設一二叉樹的:
中序 S:E D F B A G J H C I
^start1 ^j ^end1
後序 T:E F D B J H G I C A
^start2 ^end2
node *create(char *s,char *t, int start1,int start2,int end1,int end2)
{ if (start1>end1) return NULL; //迴歸條件
root=(node *)malloc(sizeof(node));
root->data=t[end2];
找到S中T[end2]的位置爲 j
root->lch=create(S,T,s1,j-1,start1,j+start2-start1-1);
root->rch=create(S,T,j+1,end1,j+start2-start1,end2-1);
return root;
}
例5.組合問題
n 個數: (1,2,3,4,…n)求從中取r個數的所有組合.
設n=5,r=3;
遞歸思想:先固定一位 5 (從另四個數當中選二個)
5,4 (從另三個數當中選一個)
5,4,3 (從另二個數當中選零個)
即:n-2個數中取r-2個數的所有組合
…
程序:
void combire(int n,int r) {
for(k=n;k>=n+r-1;k--) {
a[r]=k;
if(r==0) 打印a數組(表示找到一個解);
else combire(n-1,r-1);
}
}
第五天 9/28/2003
回溯法:
回溯跟遞歸都是程序員考試裏常出現的問題,大家必須掌握!
回溯法的有關概念:
1) 解答樹:葉子結點可能是解,對結點進行後序遍歷.
2) 搜索與回溯
五個數中任選三個的解答樹(解肯定有三層,至葉子結點):
ROOT 虛根
/ / | \ \
1 2 3 4 5
/ | | \ / | \ /\ |
2 3 4 5 3 4 5 4 5 5
/|\ /\ | /\ | |
3 4 5 4 5 5 4 5 5 5
回溯算法實現中的技巧:棧
要搞清回溯算法,先舉一個(中序遍歷二叉樹的非遞歸算法)來說明棧在非遞歸中所起的作用。
A 過程:push()->push()->push()->push()棧內結果:ABDE(E爲葉子,結束進棧)
/ \ pop() ABD(E無右孩子,出棧)
B C pop() AB(D無右孩子,出棧)
/\ pop() A(B有右孩子,右孩子進棧)
D F . .
/ /\ . .
E G H . .
/ . .
I 最後結果: EDBGFIHAC
簡單算法:
…
if(r!=NULL) //樹不空
{ while(r!=NULL)
{ push(s,r);
r=r->lch; //一直向左孩子前進
}
while(!empty(s)) // 棧非空,出棧
{ p=pop(s);
printf(p->data);
p=p->rch; //向右孩子前進
while(p!=NULL)
{ push(s,p);
p=p->lch; //右孩子進棧
}
}
} //這就是傳說中的回溯,嘻嘻……沒嚇着你吧
5選3問題算法:
思想: 進棧:搜索
出棧:回溯
邊建樹(進棧)邊遍歷(出棧)
基本流程:
太複雜了,再說我不太喜歡用WORD畫圖(有損形象),以後再整理!
程序: n=5;r=3
……
init(s) //初始化棧
push(s,1) //根進棧
while(s.top<r-1)&&(s.data[s.top]!=n) //有孩子
push(s,s.data[s.top]+1); //孩子入棧
while(!empty(s))
{ if(s.top=r-1)
判斷該"解"是否爲解.
x=pop(s); //保留x,判斷是否爲最大值n,如果是n,則出棧
while(x==n)
x=pop(s);
push(s,x+1);
while(s.top<r-1)&&(s.data[s.top]!=n)
push(s,s.data[s.top]+1);
}
揹包問題: TW=20 , w[5]={6,10,7,5,8}
解的條件:1) 該解答樹的葉子結點
2) 重量最大
解答樹如下: ROOT
/ | | | \
6 10 7 5 8
/ | | \ / | \ / \ |
10 7 5 8 7 5 8 5 8 8
| | |
5 8 8
程序:
temp_w 表示棧中重量和
…
init(s); //初始化棧
i=0;
While(w[i]>TW)
i++;
If(i==n) Return -1; //無解
Else {
Push(s,i);
Temp_w=w[i];
i++;
while(i<n)&&(temp_w+w[i]<=TW)
{ push(s,i);
temp_w+=w[i];
i++;
}
max_w=0;
while(!empty(s))
{ if(max_w<temp_w)
max_w=temp_w;
x=pop(s);
temp_w-=w[x];
x++;
while(x<n)&&(temp_w+w[x]>TW)
x++;
while(x<n)
{ push(s,x);
temp_w=temp_w+w[x];
x++;
while(x<n)&&(temp_w+w[x]>TW)
x++;
}
}
請大家思考:四色地圖問題,比如給中國地圖塗色,有四種顏色,每個省選一種顏色,相鄰的省不能取同樣的顏色.不許偷懶,不能選人口不多於xxxxW的"大國"哦!如果真的有一天台灣獨立了,下場就是這樣了,一種顏色就打發了,不過臺灣的程序員們賺到了,省事!呵呵。
貪婪法:
不求最優解,速度快(以精確度換速度)
例:哈夫曼樹,最小生成樹
裝箱問題:
有n個物品,重量分別爲w[n],要把這n個物品裝入載重爲TW的集裝箱內,需要幾個集裝箱?
思想1:對n個物品排序
拿出第1個集裝箱,從大到小判斷能不能放。
2 …
3 …
. …
. …
思想2: 對n個物品排序
用物品的重量去判斷是否需要一隻新箱子,如果物品重量小於本箱子所剩的載重量,則裝進去,反之則取一隻新箱子。
程序:
count=1;qw[0]=TW;
for(i=0;i<n;i++)
{
k=0;
while(k<count)&&(w[i]>qw[k])
k++;
if(w[i]<=qw[k])
qw[k]=qw[k]-w[i];
code[i]=k; //第i個物品放在第k個箱子內
else
{count++; //取一個新箱子
qw[count-1]=TW-w[i];
code[i]=count-1;
}
}
用貪婪法解揹包問題:
n個物品,重量:w[n] 價值v[i]
揹包限重TW,設計一個取法使得總價值最大.
方法:
0 1 2 3 … n-1
w0 w1 w2 w3 … wn-1
v0 v1 v2 v3 … vn-1
v0/w0 … v(n-1)/w(n-1) 求出各個物品的"性價比"
先按性價比從高到低進行排序
已知:w[n],v[n],TW
程序:
…
for(I=1;I<n;I++)
d[i]=v[i]/w[i]; //求性價比
for(I=0;I<n;I++)
{ max=-1;
for(j=0;j<n;j++)
{ if(d[j]>max)
{ max=d[j];x=j; }
}
e[i]=x;
d[x]=0;
}
temp_w=0;temp_v=0;
for(i=0;i<n;i++)
{ if(temp_w+w[e[i]]<=TW)
temp_v=temp_v+v[e[v]];
}
分治法:
思想:把規模爲n的問題進行分解,分解成幾個小規模的問題.然後在得到小規模問題的解的基礎上,通過某種方法組合成該問題的解.
例:數軸上有n個點x[n],求距離最小的兩個點.
分:任取一點,可以把x[i]這n個點分成兩個部分
小的部分 分點 大的部分
|_._.__.__.____._|__._._.__._.__._______._.__._._.__.___._____._|
治:解=min{小的部分的距離最小值;
大的部分的距離最小值;
大的部分最小點和小的部分最大點這兩點之差;}
第六天(想起了兒時的《最後一課》^_^) 10/7/2003
快考試了,老師沒有多說什麼,他只是給我們講了講近三年試題情況,並詳細講述了兩道題目:一道遞歸,另一道回溯。不過今天不知怎麼回事,我感覺特別累,也特別想睡覺。所以沒什麼感覺。這裏就不說了,兩道題目都有的,遞歸那題是2001年最後一題,回溯是在《程序員教程》P416,老師說高程曾經考過,有興趣自己看看也能看懂的。老師特意爲我們出了一份下午試題,我現在把他拿出來讓大家參考參考。到這裏,就到這裏!
程序員考試下午試題(模擬)
一、把一個字符串插入到另一個字符串的某個位置(指元素個數)之後
char *insert(char *s,char *t,int position)
{ int i;
char *target;
if(position>strlen(t)) printf("error");
else
{ for (i=0;i< (1) ;i++)
{ if (i<position)
target[i]=s[i];
else
{ if(i< (2) )
target[i]=t[i];
else (3) ;
}
}
}
return garget;
}
二、輾轉相除法求兩個正整數的最大公約數
int f(int a,int b)
{ if (a==b) (4) ;
else
{ if (a>b) return f(a-b,b);
else (5) ;
}
}
三、求一個鏈表的所有元素的平均值
typedef struct { int num;
float ave;
}Back;
typedef struct node{ float data;
struct node *next;
} Node;
Back *aveage(Node *head)
{ Back *p,*q;
p=(Back *)malloc(sizeof(Back));
if (head==NULL)
{ p->num=0;
p->ave=0; }
else
{ (6) ;
p->num=q->num+1;
(7) ; }
retuen p;
}
main()
{ Node *h; Back *p;
h=create(); /*建立以h爲頭指針的鏈表*/
if (h==NULL) printf("沒有元素");
else { p=aveage(h);
printf("鏈表元素的均值爲:%6f",p->ave);
}
}
四、希爾排序
已知待排序序列data[n];希爾排序的增量序列爲d[m],其中d[]序列降序排列,且d[m-1]=1。其方法是對序列進行m趟排序,在第i趟排序中,按增量d[i]把整個序列分成d[i]個子序列,並按直接插入排序的方法對每個子序列進行排序。
希爾排序的程序爲:
void shellsort(int *data,int *d,int n,int m)
{ int i,j;
for (i=0;i<m;i++)
for (j=0; (1) ;j++)
shell( (2) );
}
void shell(int *data,int d,int num,int n)
{ int i,j,k,temp;
for (i=1; (3) ;i++)
{ j=0;
temp=data[j+i*d];
while ((j<i)&&( (4) ))
j++;
for (k=j;k<i;k++)
data[k+1]=data[k];
(5) ;
(6) }
}
五、求樹的寬度
所謂寬度是指在二叉樹的各層上,具有結點數最多的那一層上的結點總數。本算法是按層次遍歷二叉樹,採用一個隊列q,讓根結點入隊列,最後出隊列,若有左右子樹,則左右子樹根結點入隊列,如此反覆,直到隊列爲空。
int Width(BinTree *T)
{ int front=-1,rear=-1; /* 隊列初始化*/
int flag=0,count=0,p;/*p用於指向樹中層的最右邊的結點,flag記錄層中結點數的最大值。*/
if(T!=Null)
{ rear++; (1) ; flag=1; p=rear;
}
while( (2) )
{ front++;
T=q[front];
if(T->lchild!=Null)
{ rear++; (3) ; count++; } //
if(T->rchild!=Null)
{ rear++; q[rear]=T->rchild; (4) ; }
if(front==p) /* 當前層已遍歷完畢*/
{ if( (5) ) flag=count; count=0; //
p=rear; /* p指向下一層最右邊的結點*/
}
}
return(flag);
}
六、區間覆蓋
設在實數軸上有n個點(x0,x1,……,xn-2,xn-1),現在要求用長度爲1的單位閉區間去覆蓋這n個點,則需要多少個單位閉區間。
int cover(float x[ ], int num)
{ float start[num],end[num];
int i ,j ,flag, count=0;
for (i=0;i<num;i++)
{ flag=1;
for (j=0;j< (1) ;j++)
{ if ((start[j]>x[i])&&(end[j]-x[i]<=1)) (2) ;
else if ( (3) ) end[j]=x[i];
else if ((x[i]>start[j])&&(x[i]<end[j])) flag=0;
if (flag) break;
}
if ( (4) )
{ end[count]=x[i]; (5); count++; }
}
return count-1;
}
start[count]=x[i]
七、圍棋中的提子
在圍棋比賽中,某一方(假設爲黑方)在棋盤的某個位置(i,j)下子後,有可能提取對方(白方的一串子)。以W[19][19]表示一個棋盤,若W[i][j]=0表示在位置(i,j)上沒有子,W[i][j]=1表示該位置上的是黑子,W[i][j]=-1表示該位置上是白子。可以用回溯法實現提子算法。
下列程序是黑棋(tag=1)下在(i,j)位置後判斷是否可以吃掉某些白子,這些確定可以提掉的白子以一個線性表表示。
問題相應的數據結構有:
#define length 19 /*棋盤大小*/
#define max_num 361 /*棋盤中點的數量*/
struct position { int row; int col;
}; /*棋子位置*/
struct killed { struct position data[max_num]; int num;
} *p; /*存儲可以吃掉的棋子位置*/
struct stack { struct position node[max_num]; int top;
}; /*棧*/
int w[length][length]; /*棋盤中雙方的棋子分佈*/
int visited[length][length]; /*給已搜索到的棋子位置作標記,初值爲0,搜索到後爲1*/
struct killed *kill(int w[length][length],int r,int c,int tag)
{ struct killed *p;
struct position *s;
struct stack S;
for (i=0;i<length;i++)
for (j=0;j<length;j++)
(1) ;
S.top=-1; p->num=-1;
if (w[r-1][c]==tag*(-1)) s->row=r-1; s->col=c;
else if (w[r+1][c]==tag*(-1)) s->row=r+1; s->col=c;
else if (w[r][c-1]==tag*(-1)) s->row=r; s->col=c-1;
else if (w[r][c+1]==tag*(-1)) s->row=r; s->col=c+1;
else p->len=0; return p;
push(S,s); visited[s->row][s->col]=1;
flag=search(s,tag);
while ( (2))
{ push(S,s); visited[s->row][s->col]=1;
(3);
}
while (S->top>=0)
{ pop(S);
(4);
flag=search(s,tag);
while (flag)
{ push(S,s);
visit(s);
flag=search(s);
}
}
}
void push( struct stack *S, struct position *s)
{ S->top++;
S->node[S->top].row=s->row;
S->node[S->top].col=s->col;
p->num++;
p->data[p->num].row=s->row;
p->data[p->num].col=s->col;
}
void pop(struct stack *S)
{ S->top--;
}
struct position *gettop(struct stack *S)
{ struct position *s;
s->row=S->data[S->top].row;
s->row=S->data[S->top].row;
return s;
}
int search(struct position *s,int tag)
{ int row,col;
row=s->row; col=s->col;
if (W[row+1][col]=(-1)*tag)&&(!visited[row+1][col])
{ s->row=row+1;s->col=col; return 1;}
if (W[row-1][col]=(-1)*tag)&&(!visited[row-1][col])
{ s->row=row-1;s->col=col; return 1;}
if (W[row][col+1]=(-1)*tag)&&(!visited[row][col+1])
{ s->row=row;s->col=col+1; return 1;}
if (W[row][col-1]=(-1)*tag)&&(!visited[row][col-1])
{ s->row=row;s->col=col-1; return 1}
(5);
}
答案:
(1)strlen(s)+strlen(t) (2)position+strlen(t) (3)target[i]=s[i-strlen(t)]
(4)return a (5)return f(a,b-a)
(6)q=aveage(head->next) (7)p->ave=(head->data+q->ave*q->num)/p->num
(1)j<d[i] (2)data,d[i],j,n (3)num+i*d<n (4)data[j+i*d]<temp (5)data[j]=temp
(1)q[rear]=T (2)front<p (3)q[rear]=T->lchild (4)count++ (5)flag<count
(1)count (2)(x[i]>end[j])&&(x[i]-start[j]<=1) (3)start[j]=x[i] (4)!flag (5)
(1)visited[i][j]=0 (2)flag (3)flag=search(s,tag) (4)s=gettop(S) (5)return 0
課已全部授完,也該收筆了.望對大家有所啓發吧!
最後祝大家順利過關