實習、校招常考算法一:二叉樹遍歷、排序算法

1、二叉樹遍歷 node

一、前序遍歷(PreOrder)\中序遍歷(InOrder)\後序遍歷(PostOrder) ios

void PreOrderTraverse(BinaryTreeNode T)
{  
     if(T == NULL)return;
     cout<<T->node);
     PreOrderTraverse(T->lChild);
     PreOrderTraverse(T->lChild);
}
二、已知後序、中序,求前序遍歷
#include<iostream.h>  
#include<string.h>   
#include<stdlib.h>   
#define MAX 20 /*預約義字符數組的最大長度*/  
typedef struct BiTNode /*該結構體類型爲樹結點的類型*/  
{    
       char data;  
       struct tnode *lchild;    
       struct tnode *rchild;    
}BiTNode,*BiTree;  
void in_post_to_bt(char *in, char *post,int len,BiTree &T)
{
    int k;
	if(len<=0){
	    T = NULL;
		return;
	}
	for(char *temp=in;temp<in+len;temp++)//在中序序列in中找到根節點所在的位置
	    if(*(post+len-1) == *temp){
		  k = temp-in;//k爲根節點在中序序列中的下標
		  T = new BiTNode ();
		  T->data = *temp;
	          break;
	     }}
	in_post_to_bt(in,post,k,T->lchild);//創建左子樹
	in_post_to_bt(in+k+1,post+k,len-k-1,T->rchild);//創建右子樹
}
void preOrder_visit(BiTree T)
{
    if(T)
	{
	    cout<<T->data;
	    inOrder_visit(T->lchild);
	    inOrder_visit(T->rchild);
	}
}
int main()  
{    
       char in[MAX+1],post[MAX+1];    
       cout<<"輸入中序序列:";   
       cin>>in;   
       cout<<"輸入後序序列:";   
       cin>>post;    
       BiTNode T;   
       int len_in=strlen(in),len_post=strlen(post);   
       if(len_in<=MAX&&len_post<=MAX)    
              in_post_to_bt(in,post,len_in,T);  
       cout<<endl<<"輸出前序序列:";    
       preOrder_visit(T);  
       cout<<endl;   
}

二、排序算法 算法

七種排序算法,按複雜度分爲兩大類, 數組

  • 簡單算法:冒泡排序(交換)、簡單選擇排序和直接插入排序;(穩定)
  • 改進算法:希爾排序(不穩定)、堆排序(不穩定)、歸併排序(穩定)和快速排序(不穩定);
//排序用到的順序表
#define MAXSIZE 10
typedef struct
{
   int r[MAXSIZE+1] ;
   int length;
}SqList;
//排序中大量用到數組交換
void swap(SqList *L,int i,int j)
{
    int temp = L->r[i];
	L->r[i] = L->r[j];
	L->r[j] = temp;
}
(1)冒泡排序:兩兩比較相鄰記錄的關鍵字,若是反序則交換,直到沒有反序的記錄爲止。

優化的冒泡排序算法 post

void BubbleSort(SqList *L)
{
   int i,j;
   bool flag = true;//flag用來作標記
   for(i=1,i<=L->length&&flag;i++){//若是flag爲false則推出循環
      flag = false;
      for(j=L->length-1;j>=i;j--){
          if(L->r[j]>L->[j+1])
          {
             swap(L,j,j+1);
             flag = true;
          }
      }
    }
}

複雜度分析:增長了一個額外空間flag開銷,若表自己有序,則根據改進後的代碼,可推斷就是n-1次比較,時間複雜度爲O(n),最壞狀況是,表逆序,比較1+2+3+。。。+(n-1)=(n-1)n/2,並做等數量及的移動,總時間複雜度爲O(n^2),是一種穩定的排序算法。 性能

(2)簡單選擇排序:經過n-i次關鍵字間的比較,從n-i+1個記錄中選出關鍵字最小的記錄,並和第i(1<=i<=n)個記錄交換。 優化

void SelectSort( SqList *L)
{
    int i,j,min;
	for(i=1;i<L->length;i++){
	    min = i;
		for(j=i;j<=L->length;j++){
		    if(L->r[min]>L->r[j])
			    L->r[min]=L->r[j];
		}
		if(i!=min)
		swap(L,i,min);
	}
}
複雜度分析:增長一個min空間開銷,不管最好最差的狀況,其比較次數同樣多,第i趟排序須要進行n-i此關鍵字比較,此時須要比較(n-1)+(n-2)+...+1=n(n-1)/2。而對於交換次數而言,當最好的時候,交換爲0次,最差的時候,也就是初始降序,交換次序爲n-1次,最終的排序時間是比較和交換的次數總和,所以,總的時間複雜度依然爲O(n^2)。儘管與冒泡排序同爲 O(n^2),但簡單選擇排序的性能仍是略優於冒泡排序的,是一種穩定的排序算法

(3)直接插入排序:將一個記錄插入到已經排好序的有序表中,從而獲得一個新的、記錄數增1的有序表。 spa

void InsertSort( SqList *L)
{
    int i,j,temp;
    for(i=2;i<=L->length;i++){
	if(L->r[i]<L-r[i-1]){       //需將L->r[i]插入有序表中
	   temp = L->r[i];          //記錄當前比較元素
	   for(j=i-1;L->[j]>temp;j++) //有序部分記錄後移
	       L->r[j+1]=L->r[j];
	   L->r[j+1]=temp;
	}
    }
}
複雜度分析:一個記錄輔助空間開銷,最好時即自己有序,比較次數即第六行每一個L.r[i]和L.r[i-1]的比較,因爲每次都是 L.r[i]>L.r[i-1],因此不用比較,時間複雜度爲O(n)。最壞時即自己逆序,須要比較2+3+4+...+n=(n+2)(n+3)/2次,而記錄的移動次數也達到了最大值(n+4)(n-1)/2。若是記錄是隨機的,那根據機率相同的原則,平均比較次數和移動次數約爲n^2/4次。所以,咱們得出直接插入排序的時間複雜度爲O(n^2)。一樣的時間複雜度,直接插入比冒泡排序、簡單選擇排序的性能要好一些, 是一種 穩定 的排序算法

(4)希爾排序:跳躍分割,基本有序(小的關鍵字基本在前面,大的基本在後面)在直接插入排序基礎上,將相距某個「增量」的記錄組成一個子序列。 code

void InsertSort( SqList *L)
{
    int i,j,temp,
	int increment = L->length;
	do{
	    increment = increment/3+1;        //增量序列
	    for(i=increment+1;i<=L->length;i++){
	         if(L->r[i]<L-r[i-increment]){       //需將L->r[i]插入增量有序表中
		        temp = L->r[i];          //記錄當前比較元素
		        for(j=i-increment;L->[j]>temp;j++)//有序部分記錄後移
		           L->r[j+increment]=L->r[j];
		        L->r[j+1]=temp;
		    }
	    }
	}while(increment>1);
}
複雜度分析:增量的選取很關鍵,當增量序列爲dlta[k]=2^(t-k+1)-1(0<=k<=t<=log2(n+1))增量序列的最後一個增量值必須等於1才行。希爾排序一種 不穩定的排序算法,能夠得到好的效果,其時間複雜度爲O(n^3/2)。

(5)堆排序:將待排序的序列構形成一個大頂堆。此時,整個序列的最大值就是對頂的根節點。將它移走(其實就是將其與堆數組末尾的元素交換,此時末尾元素就是最大值),而後將剩餘的n-1個序列從新構形成一個堆,這樣就會獲得n個元素中的次大值。如此反覆執行,能獲得一個有序序列。。 排序

void InsertSort( SqList *L)
{
    int i;
	for(i=L->length/2;i>0;i--)    //把L中的r構建成一個大頂堆
	    HeapAdjust(L,i,L->length);
	for(i=L->length;i>1;i--){     //將堆頂記錄和當前未經排序子序列的最後一個記錄交換
	    swap(L,i,j);          
		HeapAdjust(L,1,i-1);      //將L->r[1,...i-1]從新調整爲大頂堆
    }
}
void HeapAdjust(SqList *L,int start,int end)
{
     int temp,j;
	 temp = L->r[start];
	 for(j=2*start;j<=end;j*=2){//沿關鍵字較大的孩子結點向下篩選
	    if(j<end && L->r[j]<r->r[j+1])
		   j++;
		if(temp>=L-r[j])
		   break;
		L->r[start] = L->r[j];
		start = j;
	}
	L->r[start]=temp;
}

複雜度分析:運行時間主要消耗在初始建堆和在重複建堆時的反覆篩選上。由於咱們是徹底二叉樹從最下層最右邊的非終端結點開始構建,將它與其孩子進行比較如有必要的互換,對於非終端結點來講,最多進行兩次比較和互換操做,所以構建堆的時間複雜度爲O(n)。正式排序時,第i次取堆頂記錄重建堆須要用O(logi)的時間,而且須要取n-1次堆頂記錄,所以,重建堆的時間複雜度爲O(nlogn)。堆排序對原始狀態不敏感,不管最好、最壞和平均時間複雜度均爲O(nlogn)。空間複雜度上,只有用一個交換的暫存單元。不過記錄的比較與交換是跳躍式的,因此堆排序也是不穩定的。

(6)歸併排序:假設初始序列含有n個記錄,則能夠當作是n個有序的子序列,每一個子序列的長度爲1,而後兩兩歸併,獲得[n/2](上表示不小於x的最小整數)個長度爲2或1的有序子序列;再兩兩歸併,......,如此重複,直至獲得一個長度爲n的有序序列爲止,這種排序方法稱爲2路歸併排序。

//對順序表L做歸併排序
void MergeSort(SqList *L)
{
     MSort(L->r,L->r,1,L->length);
}
//將SR[start...end]歸併爲有序的TR1[start...end]
void MSort(int SR[],int TR1[],int start,int end)
{
     int m;
	 int TR2[MAXSIZE+1];
	 if(start == end)
	     TR1[start] = SR[start];
	 else {
	     mid = (start+end)/2;         //將SR[start...end]平分爲SR[start...mid]和SR[mid...end]
	     MSort(SR,TR2,start,mid);     //遞歸將SR[start...mid]歸併爲有序的TR2[start...mid]
	     MSort(SR,TR2,mid+1,end);     //遞歸將SR[mid+1...end]歸併爲有序的TR2[mid+1...end]
	     Merge(TR2,TR1,start,mid,end);//遞歸將TR2[start...mid]和TR2[mid+1...end]歸併爲有序的TR1[start...end]
	}
}
//將有序的SR[i..m]和SR[m+1...n]歸併爲有序的TR[i..n]
void Merge (int SR[],int TR[],int i, int m,int n)
{
     int j,k,l;
	 for(j=m+1,k=i;i<=m&&j<=n;k++){
	     if(SR[i]<SR[j])
		     TR[k]=SR[i++];
		 else 
		     TR[k]=SR[j++];
	}
	 if(i<=m){
		for(l=0;l=m-i;l++)
			TR[k+l]=SR[i+l];
	}
	if(j<=n){
		for(l=0;l<=n-j;l++)
		    TR[k+l]=SR[j+l];
	}
}

算法複雜度:一趟歸併須要將SR[1]~SR[n]中相鄰的長度爲h的有序序列進行兩兩歸併,並將結果存放在到TR1[1]~TR1[n]中,須要將待排序序列中的全部記錄掃描一遍,所以耗費O(n)時間,而有徹底二叉樹的深度可知,整個歸併排序須要進行[log2n](上取正)次,所以,總的時間複雜度爲O(nlogn),並且是最好、最壞、平均的時間性能。

因爲歸併排序在歸併過程當中須要與原始記錄序列一樣數量的存儲空間存放歸併結果以及遞歸深度爲log2n的棧空間,所以空間複雜度爲O(n+logn)。if(SR[i]<SR[j])語句,說明不存在跳躍,則歸併排序是一種穩定的排序算法。

//歸併的非遞歸算法
void MergeSort2(SqList *L)
{
     int *TR = (int*) new (L->length*sizeof(int));
	 int k = 1;
	 while(k<L->length){
	     MergePass(L->r,TR,k,L->length);
		 k = 2*k;
		 MergePass(TR,L->r,k,L->length);
	     k = 2*k;
	}
}
void MergePass (int SR[],int TR[],int start,int end)
{
     int i = 1;
	 int j;
	 while(i<=n-2*start+1){
	    Merge(SR,TR,i,i+start-1,i+2*start-1); //兩兩歸併
	    i = i+2*start;
	}
	if(i<end-start+1)                    //歸併最後兩個序列
	    Merge(SR,TR,i,i+start-1,n);
	else
	     for(j=i;j<=n;j++)
		 TR[j] = SR[j];
}
非遞歸的迭代方法,避免了遞歸時深度爲log2n的棧空間,空間只用到申請並臨時用的TR數組。空間複雜度爲O(n)。

(7)快速排序:經過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另外一部分記錄的關鍵字小,則可分別對這兩部分記錄繼續進行排序,已達到整個序列有序的目的。

 
#include <iostream>
 
using namespace std;
 
void Qsort(int a[], int low, int high)
{
    if(low >= high)
    {
        return;
    }
    int first = low;
    int last = high;
    int key = a[first];/*用字表的第一個記錄做爲樞軸*/
 
    while(first < last)
    {
        while(first < last && a[last] >= key)
        {
            --last;
        }
 
        a[first] = a[last];/*將比第一個小的移到低端*/
 
        while(first < last && a[first] <= key)
        {
            ++first;
        }
         
        a[last] = a[first];    
/*將比第一個大的移到高端*/
    }
    a[first] = key;/*樞軸記錄到位*/
    Qsort(a, low, first-1);
    Qsort(a, first+1, high);
}
int main()
{
    int a[] = {57, 68, 59, 52, 72, 28, 96, 33, 24};
 
    Qsort(a, 0, sizeof(a) / sizeof(a[0]) - 1);/*這裏原文第三個參數要減1不然內存越界*/
 
    for(int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    {
        cout << a[i] << "";
    }
複雜度分析:在最優狀況下,每次劃分都很均勻,若是排序n個關鍵字,其地歸樹的深度爲[log2n]+1(下取正)。第一次Partition應該是須要對整個數組掃描一遍,作n次比較。而後,得到的樞紐將數組一分爲二,那麼各自還須要T(n/2)的時間(注意是最好狀況下),則最優狀況下的,快速排序算法的時間複雜度爲O(nlogn)。是一種 不穩定排序算法。

優化方案:一、優化選取樞紐:三數取中;二、優化沒必要要的交換

相關文章
相關標籤/搜索