实习、校招常考算法一:二叉树遍历、排序算法

一、二叉树遍历

1、前序遍历(PreOrder)\中序遍历(InOrder)\后序遍历(PostOrder)

void PreOrderTraverse(BinaryTreeNode T)
{  
     if(T == NULL)return;
     cout<<T->node);
     PreOrderTraverse(T->lChild);
     PreOrderTraverse(T->lChild);
}
2、已知后序、中序,求前序遍历
#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;   
}

2、排序算法

七种排序算法,按复杂度分为两大类,

  • 简单算法:冒泡排序(交换)、简单选择排序和直接插入排序;(稳定)
  • 改进算法:希尔排序(不稳定)、堆排序(不稳定)、归并排序(稳定)和快速排序(不稳定);
//排序用到的顺序表
#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)冒泡排序:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

优化的冒泡排序算法

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的有序表。

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)希尔排序:跳跃分割,基本有序(小的关键字基本在前面,大的基本在后面)在直接插入排序基础上,将相距某个“增量”的记录组成一个子序列。

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)快速排序:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,已达到整个序列有序的目的。

void QuickSort(SqList *L)
{
    QSort(L,1,L->length);
}
void QSort(SqList *L,int low,int high)
{
    int pivot;
    if(low<high){
	pivot = Partition(L,low,high);
	QSort(L,low,pivot-1);
	QSort(L,pivot+1,high);
    }
}
int Partition (SqList *L,int low,int high)
{
    int pivotkey;
    pivotkey = L->r[low];
    while(low<high){
	 while(low<high && L->r[high])>= pivotkey)
	   high--;
	 while(low<high && L->r[low])<= pivotkey)
	   low++;
        swap(L,low,high);
    }
    return low;
}
复杂度分析:在最优情况下,每次划分都很均匀,如果排序n个关键字,其地归树的深度为[log2n]+1(下取正)。第一次Partition应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢纽将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好情况下),则最优情况下的,快速排序算法的时间复杂度为O(nlogn)。是一种 不稳定排序算法。

优化方案:1、优化选取枢纽:三数取中;2、优化不必要的交换

你可能感兴趣的:(实习、校招常考算法一:二叉树遍历、排序算法)