每日算法之2
1.之兩個非遞減的數組合併爲一個數組保持依然有序
//問題描述:兩個非遞減數組,A,B,A具有足夠的內存容納B,要求將兩個數組合併爲一個數組,保持數組依然非遞減
//算法分析:從B數組中第一個元素開始,依次與數組A的尾部至頭部開始掃描比較,如果小於,則後移一位,下標前移一位,繼續比較,直到找到非小於的位置,將其插入當前的後一位;遍歷完B數組,則循環結束
//算法實現;
void mergeSort(int A[],int B[],int a_length,int b_length){
if(A==NULL || B==NULL ){
cout<<"error!"<<endl;
return;
}
if(a_length>b_length){
int j=a_length-b_length-1;//表示指向A數組的下標
int i;//表示指向B數組的下標
int k=a_length-1;
for(i=b_length-1;i>=0 && j>=0;--i){//表示遍歷B數組的元素
for(j;j>=0;--j){
if(B[i]>=A[j]){
A[k--]=B[i];
break;
}
else
A[k--]=A[j];
}
}
if(i+1>=0){//將B數組剩餘元素從頭開始依次填入A數組,記得由於上次循環退出時,是自動減了1
for(int p=0;p<=i+1;++p){
A[p]=B[p];
}
}
}
}
int main(){
//測試用例
int a[10]={4,5,6,8,10,34};
int b[4]={3,5,9,18};
mergeSort(a,b,10,4);
for(int i=0;i<10;++i)
cout<<a[i]<<" ";
}
//雖然使用了兩個循環,但是沒執行一次比較,必會幹掉一個最大值放在後面,也就是說,從思路上就能判斷出基本操作次數最壞情況下爲兩個序列的長度和,所以時間複雜度爲O(n),即線性時間
//其他優秀的算法代碼分析:
/**
* 合併兩個有序數組,合併後仍然有序
* @param a 要合併的數組A
* @param b 要合併的數組B
* @param c 合併後的數組C
*/
void merge(int a[] ,int b[],int c[]){
//1.傳入函數的參數必須考慮周全,傳入什麼參數,有什麼作用,考慮是值傳遞還是引用傳遞,需不需
//要加const常量進行限定
int lengthA = a.length;
int lengthB = b.length;
//2.首先將此函數需要使用的局部變量定義好,標識符要有實際意義,儘量避免使用i,k,l,否則
//閱讀你的代碼真是一件頭疼的事情
int indexA = 0;
int indexB = 0;
int indexC = 0;
//3.使用循環必須充分考慮好退出條件,是否存在其他退出循環的情況
while(indexA < lengthA && indexB < lengthB){
if(a[indexA] < b[indexB]){
c[indexC++] = a[indexA++];
}else{
c[indexC++] = b[indexB++];
}
}
//4.當退出循環時,這個遍歷指針或者下標是否自動前移?
//此外,當循環結束,是否真的完成了?
while(indexA < lengthA){
c[indexC++] = a[indexA++];
}
while (indexB < lengthB) {
c[indexC++] = b[indexB++];
}
//5.使用循環最頭疼的就是邊界條件,只要記住一點,編號,第幾號和個數,第幾個不要混用就OK了
}
2.之鏈表操作-反向輸出鏈表
鏈表的特點:是一種動態的數據結構,傳入鏈表時,只需要傳入頭結點指針即可,不需要指定長度
//問題描述:輸入一個鏈表,將一個帶頭結點的鏈表反轉輸出
//第一種方法
struct LNode{
int data;
struct LNode *next;
};
void reverse_list(LNode *A){
LNode *curLNode,*nextLNode,*tempLNode;//定義當前指針和下一個指針以及一個臨時指針
curLNode=A->next;
nextLNode=curLNode->next;//進行初始化操作
curLNode->next=NULL;
while(nextLNode!=NULL){//當下一個節點爲空時,退出循環
tempLNode=curLNode;
curLNode=nextLNode;
nextLNode=nextLNode->next;//每次反轉之前需要後移一位,並使用臨時變量記住上次的節點
curLNode->next=tempLNode;//將當前的節點指針反轉指向臨時節點
}
A->next=curLNode;//最後將頭結點指針重新指向反轉後的第一個節點
}
void createList(LNode *A,const int data[],int length){
//1.使用頭插法創建一個鏈表,並將一個數組賦值給鏈表節點的數據域,由於使用的是頭插法,所以數組的數據順序
//是逆序的!!
LNode *firstLNode;//定義一個指向鏈表第一個節點的指針
LNode *newLNode;//定義一個需要插入個新節點指針
A=new LNode;//由於傳入的只是一個指向節點的指針,所以創建時必須new出一個指針,並指向它!!!
A->next=NULL;//初始化頭結點指針
for(int i=0;i<length;++i){
newLNode= new LNode;
newLNode->data=data[i];
firstLNode=A->next;
newLNode->next=firstLNode;
A->next=newLNode;//第一次雖然指向空節點,但正好將尾節點初始化爲NULL,一舉兩得!!
}
//2.使用尾插法正向插入數組數據
LNode *lastLNode;
LNode *newLNode;
A=new LNode;
A->next=NULL;
lastLNode=A;//初始化尾節點
for(int i=0;i<length;++i){
newLNode= new LNode;
newLNode->data=data[i];
lastLNode->next=newLNode;
lastLNode=lastLNode->next;
}
lastLNode->next=NULL;//插入完畢必須初始化尾節點指針域
}
//打印鏈表
void printfList( LNode *A){
LNode *index;
for(index=A;index->next!=NULL;index=index->next){//由於是帶頭結點的鏈表
cout<<index->next->data<<" ";//index爲當前指針節點,但是打印輸出時卻是下一個節點
}
cout<<endl;
}
//測試用例:
int main(){
int a[10]={1,2,3,4,5,6,7,8,9,10};
LNode *list;
createList(list,a,10);
printfList(list);
reverse_list(list);
printfList(list);
}
//第二種方法
//第一種方法需要修改鏈表,如果要求不能修改數據結構,改變策略,由於要求將鏈表逆序輸出,可以先正向遍歷
//將其存入一個棧中,利用棧的後進先出的特點,從而實現逆序
//算法實現:
void reversePrintfList_useListStack(LNode *A){//必須使用一個鏈表棧實現
LNode *indexA,*indexS;//鏈表A和S的指針迭代器
LNode *stack_t_LNode;//棧的頭結點
LNode *new_s_LNode;//棧的新插入的節點
LNode *first_s_LNode;//表示棧的第一個節點
stack_t_LNode=new LNode;
stack_t_LNode->next=NULL;//初始化鏈表棧,必須使用頭插法
for(indexA=A;indexA->next!=NULL;indexA=indexA->next){
new_s_LNode=new LNode;
new_s_LNode->data=indexA->next->data;//壓入棧
first_s_LNode=stack_t_LNode->next;
new_s_LNode->next=first_s_LNode;
stack_t_LNode->next=new_s_LNode;//使用頭插法連接到棧中
}
//將棧遍歷輸出
for(indexS=stack_t_LNode;indexS->next!=NULL;indexS=indexS->next){
cout<<indexS->next->data<<" ";
}
cout<<endl;
delete[] stack_t_LNode;
}
//第三種方法
//既然使用自定義鏈表棧實現反向輸出,爲何不使用一個簡單的遞歸函數,直接兩三行代碼實現遞歸棧的反轉輸出
void reversePrintfList_useRecursion(LNode *A){
if(A!=NULL){
if(A->next!=NULL){
reversePrintfList_useRecursion(A->next);
}
cout<<A->data<<" ";
}
}//該辦法有一個缺點,如果將帶頭結點的鏈表傳入,頭結點的data域也會被訪問打印,解決辦法只能再調用的時候,將頭結點過濾掉!!!
//比如:
reversePrintfList_useRecursion(A->next);
3.之二叉樹的三種遍歷考察[重點]
//掌二叉樹的八大遍歷實現,構建;;掌握完全二叉樹搜索樹,最大小堆,紅黑樹,字典樹
//使用先序遍歷構建二叉樹
typedef struct BTNode{
char data;
struct BTNode *lchild,*rchild;
}BTNode;
//邏輯存在嚴重的錯誤!!
//算法分析:使用先序遍歷將字符串數組"ABDG##H###CE##F##"構建成一棵二叉樹
void createBTree_usePreorder(BTNode *B,char *data){
int index_data;
index_data=0;
if(data[index_data]!='\0'){
if(data[index_data]=='#')
B=NULL;//表示爲葉節點
else{
B=new BTNode;
B->data=data[index_data];
createBTree_usePreorder(B->lchild,data+1);
createBTree_usePreorder(B->rchild,data+1);
}
}
}
//二叉樹的四種遍歷方式:三種深度優先的遞歸實現和藉助棧的非遞歸實現;
//一種藉助隊列實現廣度優先的非遞歸實現
//1.先序遍歷的遞歸實現和非遞歸實現
//遞歸實現:
void BTree_preorder(BTNode *B){
if(B!=NULL){
cout<<B->data<<" ";
BTree_preorder(B->lchild);
BTree_preorder(B->rchild);
}
}