算法汇总

来源:互联网 发布:免费的群发短信软件 编辑:程序博客网 时间:2024/06/06 18:21
1,怎样把一个单链表反序?
(1)反转一个链表。循环算法。
List reverse(List n)  {    if(!n)             //判断链表是否为空,为空即退出。   {     return n;   }   list cur = n.next;        //保存头结点的下个结点    list pre = n;          //保存头结点   list tmp;   pre.next = null;         //头结点的指针指空,转换后变尾结点   while ( NULL != cur.next )     //循环直到cur.next为空   {     tmp = cur;          
    tmp.next = pre;         pre = tmp;     cur = cur.next;   }   return tmp;          //f 返回头指针 } (2)反转一个链表。递归算法。 List *reverse( List *oldList, List *newHead = NULL )  {   List *next = oldList-> next;      //记录上次翻转后的链表   oldList-> next = newHead;     //将当前结点插入到翻转后链表的开头   newHead = oldList;        //递归处理剩余的链表    return ( next==NULL )? newHead: reverse( t, newHead );  } 
 2,能否用两个栈实现一个队列的功能
结点结构体: typedef struct node {   int data;   node *next; }node,*LinkStack; 创建空栈: LinkStack CreateNULLStack( LinkStack &S) {   S = (LinkStack)malloc( sizeof( node ) );          //申请新结点   if( NULL == S)   {     printf("Fail to malloc a new node.\n");     return NULL;   }    S->data = 0;                  //初始化新结点   S->next = NULL;    return S; } 栈的插入函数: LinkStack Push( LinkStack &S, int data) {   if( NULL == S)              //检验栈   {     printf("There no node in stack!");     return NULL;   }    LinkStack p = NULL;   p = (LinkStack)malloc( sizeof( node ) );      //申请新结点   if( NULL == p)   {     printf("Fail to malloc a new node.\n");     return S;   }   if( NULL == S->next)   {     p->next = NULL;   }   else   {     p->next = S->next;   }   p->data = data;             //初始化新结点   S->next = p;          //插入新结点    return S; } 出栈函数: node Pop( LinkStack &S) {   node temp;   temp.data = 0;   temp.next = NULL;    if( NULL == S)              //检验栈   {     printf("There no node in stack!");     return temp;   }   temp = *S;   if( S->next == NULL )   {     printf("The stack is NULL,can't pop!\n");     return temp;     }    LinkStack p = S ->next;            //节点出栈   S->next = S->next->next;   temp = *p;    free( p );   p = NULL;    return temp;    } 双栈实现队列的入队函数: LinkStack StackToQueuPush( LinkStack &S, int data) {   node n;   LinkStack S1 = NULL;   CreateNULLStack( S1 );            //创建空栈      while( NULL != S->next )            //S出栈入S1   {     n = Pop( S );     Push( S1, n.data );   }   Push( S1, data );                //新结点入栈    while( NULL != S1->next )           //S1出栈入S   {     n = Pop( S1 );     Push( S, n.data );   }    return S; } 
3,计算一颗二叉树的深度
int depth(BiTree T) {   if(!T) return 0;            //判断当前结点是否为叶子结点   int d1= depth(T->lchild);        //求当前结点的左孩子树的深度   int d2= depth(T->rchild);        //求当前结点的右孩子树的深度   return (d1>d2?d1:d2)+1; }
4,编码实现冒泡排序
 #include <stdio.h> #define LEN 10               //数组长度 void main( void ) {   int ARRAY[10] = { 0, 6, 3, 2, 7, 5, 4, 9, 1, 8 };  //待排序数组   printf( "\n" );   for( int a = 0; a < LEN; a++ )        //打印数组内容   {     printf( "%d ", ARRAY[a] );   }   int i = 0;   int j = 0;   bool isChange;             //设定交换标志   for( i = 1; i < LEN; i++ )   {                  //最多做LEN-1趟排序     isChange = 0;            //本趟排序开始前,交换标志应为假     for( j = LEN-1; j >= i; j-- )        //对当前无序区ARRAY[i..LEN]自下向上扫描     {       if( ARRAY[j+1] < ARRAY[j] )         {              //交换记录         ARRAY[0] = ARRAY[j+1];   //ARRAY[0]不是哨兵,仅做暂存单元         ARRAY[j+1] = ARRAY[j];         ARRAY[j] = ARRAY[0];         isChange = 1;        //发生了交换,故将交换标志置为真       }     }     printf( "\n" );     for( a = 0; a < LEN; a++)        //打印本次排序后数组内容     {       printf( "%d ", ARRAY[a] );     }     if( !isChange )            //本趟排序未发生交换,提前终止算法     {       break;     }   }    printf( "\n" );   return; }
5,编码实现直接选择排序
#include"stdio.h" #define LEN 9 void main( void ) {   int ARRAY[LEN]={ 5, 6, 8, 2, 4, 1, 9, 3, 7 };      //待序数组    printf("Before sorted:\n");   for( int m = 0; m < LEN; m++ )            //打印排序前数组   {     printf( "%d    ", ARRAY[m] );   }   for (int i = 1; i <= LEN - 1; i++)            //选择排序   {     int t = i - 1;      int temp = 0;       for (int j = i; j < LEN; j++)       {       if (ARRAY[j] < ARRAY[t])       {          t = j;         }      }      if (t != (i - 1))     {        temp = ARRAY[i - 1];       ARRAY[i - 1] = ARRAY[t];       ARRAY[t] = temp;     }   }   printf( "\n" );   printf("After sorted:\n");   for( i = 0; i < LEN; i++ )             //打印排序后数组   {     printf( "%d    ", ARRAY[i] );   }   printf( "\n" ); }
6,编程实现堆排序
#include <stdio.h> void createHeep(int ARRAY[],int sPoint, int Len)    //生成大根堆 {   while( ( 2 * sPoint + 1 ) < Len )   {     int mPoint = 2 * sPoint + 1 ;     if( ( 2 * sPoint + 2 ) < Len )     {        if(ARRAY[ 2 * sPoint + 1 ] < ARRAY[ 2 * sPoint + 2 ] )       {         mPoint = 2*sPoint+2;       }     }     if(ARRAY[ sPoint ] < ARRAY[ mPoint ])     //堆被破坏,需要重新调整     {       int tmpData= ARRAY[ sPoint ];     //交换sPoint与mPoint的数据       ARRAY[ sPoint ] = ARRAY[ mPoint ];       ARRAY[ mPoint ] = tmpData;        sPoint = mPoint ;      }     else        {       break;                //堆未破坏,不再需要调整      }   }   return; }   void heepSort( int ARRAY[], int Len )           //堆排序 {   int i=0;    for ( i = ( Len / 2 - 1 ); i >= 0; i-- )          //将Hr[0,Lenght-1]建成大根堆   {     createHeep(ARRAY, i, Len);   }   for ( i = Len - 1; i > 0; i-- )   {     int tmpData = ARRAY[0];            //与最后一个记录交换     ARRAY[0] = ARRAY[i];     ARRAY[i] = tmpData;      createHeep( ARRAY, 0, i );          //将H.r[0..i]重新调整为大根堆     }    return; }  int main( void ) {   int ARRAY[] ={ 5, 4, 7, 3, 9, 1, 6, 8, 2};    printf("Before sorted:\n");              //打印排序前数组内容   for ( int i = 0; i < 9; i++ )   {     printf("%d ", ARRAY[i]);   }   printf("\n");    heepSort( ARRAY, 9 );              //堆排序      printf("After sorted:\n");              //打印排序后数组内容   for( i = 0; i < 9; i++ )   {     printf( "%d ", ARRAY[i] );    }   printf( "\n" );   return 0; }
7,编程实现基数排序
 #include <stdio.h> #include <malloc.h> #define LEN 8 typedef struct node              //队列结点 {   int data;   struct node * next; }node,*QueueNode;  typedef struct Queue             //队列 {   QueueNode front;   QueueNode rear; }Queue,*QueueLink;  QueueLink CreateNullQueue( QueueLink &Q)    //创建空队列 {   Q = NULL;   Q = ( QueueLink )malloc( sizeof( Queue ) );   if( NULL == Q )   {     printf("Fail to malloc null queue!\n");     return NULL;   }   Q->front = ( QueueNode )malloc( sizeof( node ) );   Q->rear    = ( QueueNode )malloc( sizeof( node ) );   if( NULL == Q->front || NULL == Q->rear )   {     printf("Fail to malloc a new queue's fornt or rear!\n");     return NULL;   }   Q->rear = NULL;   Q->front->next= Q->rear;      return Q; } int lenData( node data[], int len)        //计算队列中各结点的数据的最大位数 {   int m = 0;   int temp = 0;   int d;   for( int i = 0; i < len; i++)   {     d = data[i].data;     while( d > 0)     {       d /= 10;       temp ++;     }     if( temp > m )     {       m = temp;     }     temp = 0;   }   return m; } QueueLink Push( QueueLink &Q , node node )   //将数据压入队列 {   QueueNode p1,p;   p =( QueueNode )malloc( sizeof( node ) );   if( NULL == p )   {     printf("Fail to malloc a new node!\n");     return NULL;   }   p1 = Q->front;   while(p1->next != NULL)   {     p1 = p1->next;   }   p->data = node.data;   p1->next = p;   p->next = Q->rear;   return NULL; } node Pop( QueueLink &Q)          //数据出队列 {   node temp;   temp.data = 0;   temp.next = NULL;   QueueNode p;   p = Q->front->next;   if( p != Q->rear )   {     temp = *p;     Q->front->next = p->next;     free( p );     p = NULL;   }   return temp; } int IsEmpty( QueueLink Q) {   if( Q->front->next == Q->rear )   {     return 0;   }   return 1; } int main( void ) {    int i = 0;   int Max = 0;              //记录结点中数据的最大位数   int d = 10;   int power = 1;   int k = 0;   node Array[LEN] ={{450, NULL}, {32,NULL}, { 781,NULL}, { 57 ,NULL},组           { 145,NULL},{ 613,NULL},{ 401,NULL},{ 594,NULL}};                     //队列结点数   QueueLink Queue[10];   for( i = 0; i < 10; i++)   {     CreateNullQueue( Queue[i]);      //初始化队列数组   }   for( i = 0; i < LEN; i++)   {     printf("%d    ",Array[i].data);   }     printf("\n");    Max = lenData( Array, LEN );        //计算数组中关键字的最大位数   printf("%d\n",Max);    for(int j = 0; j < Max; j++)          //按位排序   {     if(j == 0) power = 1;     else power = power *d;      for(i = 0; i < LEN; i++)     {       k = Array[i].data /power - (Array[i].data/(power * d)) * d;       Push( Queue[k], Array[i] );     }          for(int l = 0, k = 0; l < d; l++)      //排序后出队列重入数组     {       while( IsEmpty( Queue[l] ) )       {         Array[k++] = Pop( Queue[l] );       }     }     for( int t = 0; t < LEN; t++)     {       printf("%d    ",Array[t].data);     }     printf("\n");   }    return 0; } 
 
 
 
 

(1“冒泡法”冒泡法大家都较熟悉。其原理为从a[0]开始,依次将其和后面的元素比较,若a[0]>a[i],则交换它们,一直比较到a[n]。同理对a[1],a[2],...a[n-1]处理,即完成排序。下面列出其代码:void bubble(int *a,int n){int i,j,temp;for(i=0;i<n-1;i++)for(j=i+1;j<n;j++)if(a[i]>a[j]) {temp=a[i];a[i]=a[j];a[j]=temp;}}冒泡法原理简单,但其缺点是交换次数多,效率低。下面介绍一种源自冒泡法但更效率的方法“择法”。(2“选择法”择法循环过程与冒泡法一致,它还定义了记号k=i,然后依次把a[k]同后面元素比较,若a[k]>a[j],则使k=j.最后看看k=i是否还成立,不成立则交换a[k],a[i],这样就比冒泡法省下许多无用的交换,提高了效率。void choise(int *a,int n){int i,j,k,temp;for(i=0;i<n-1;i++) {k=i;for(j=i+1;j<n;j++)if(a[k]>a[j]) k=j;if(i!=k) {temp=a[i];a[i]=a[k];a[k]=temp;}}}择法比冒泡法效率更高,但说到高效率,非“快速法”莫属,现在就让我们来了解它。(3“快速法”快速法定义了个参数,(数组首地址*a,要排序数组起始元素下标i,要排序数组结束元素下标j). 它首先一个数组元素(一般为a[(i+j)/2],即中间元素作为参照,把比它小的元素放到它的左边,比它大的放在右边。然后运用递归,在将它左,右两个子数组排序,最后完成整个数组的排序。下面分析其代码:void quick(int *a,int i,int j){int m,n,temp;int k;m=i;n=j;k=a[(i+j)/2];do {while(a[m]<k&&m<j) m++;while(a[n]>k&&n>i) n--;if(m<=n) {temp=a[m];a[m]=a[n];a[n]=temp;m++;n--;}}while(m<=n);if(m<j) quick(a,m,j);if(n>i) quick(a,i,n);}(4“插入法”插入法是一种比较直观的排序方法。它首先把数组头两个元素排好序,再依次把后面的元素插入适当的位置。把数组元素插完也就完成了排序。void insert(int *a,int n){  int i,j,temp;for(i=1;i<n;i++){temp=a[i];j=i-1;while(j>=0&&temp<a[j]){a[j+1]=a[j];j--;}a[j+1]=temp;}}自己做的插入法void insert(int*p,int n){ register int i,j,k;  for(i=1;i<n;i++)  k=*(p+i);  for(j=i-1;j>=0;j--)    if(k<*(p+j))    *(p+j+1)=*(p+j);   else    break;   *(p+j+1)=k; 

}(5“shell法”shell法是一个叫 shell 的美国人与1969年发明的。它首先把相距k(k>=1)的那几个元素排好序,再缩小k值(一般取其一半,再排序,直到k=1时完成排序。下面让我们来分析其代码:void shell(int *a,int n){int i,j,k,x;k=n/2;while(k>=1) {for(i=k;i<n;i++) {x=a[i];j=i-k;while(j>=0&&x<a[j]) {a[j+k]=a[j];j-=k;}a[j+k]=x;}k/=2;}}

堆排序

       堆排序是利用堆的性质进行的一种选择排序。下面先讨论一下堆。

1.堆

  堆实际上是一棵完全二叉树,其任何一非叶节点满足性质:

  Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]

  即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。

  堆分为大顶堆和小顶堆,满足Key[i]>=Key[2i+1]&&key>=key[2i+2]称为大顶堆,满足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆。由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。

2.堆排序的思想

   利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

    其基本思想为(大顶堆):

    1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无须区;

    2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n]; 

    3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

    操作过程如下:

     1)初始化堆:将R[1..n]构造为堆;

     2)将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

    因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。

    下面举例说明:

     给定一个整形数组a[]={16,7,3,20,17,8},对其进行堆排序。

    首先根据该数组元素构建一个完全二叉树,得到

 
 然后需要构造初始堆,则从最后一个非叶节点开始调整,调整过程如下:

20和16交换后导致16不满足堆的性质,因此需重新调整

这样就得到了初始堆。

即每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整)。有了初始堆之后就可以进行排序了。

此时3位于堆顶不满堆的性质,则需调整继续调整

 这样整个区间便已经有序了。
    从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1...n]中选择最大记录,需比较n-1次,然后从R[1...n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。
 
测试程序
复制代码
  #include <iostream>#include<algorithm> using namespace std;  void HeapAdjust(int *a,int i,int size) //调整堆 { int lchild=2*i; //i的左孩子节点序号  int rchild=2*i+1; //i的右孩子节点序号  int max=i; //临时变量  if(i<=size/2) //如果i不是叶节点就不用进行调整 { if(lchild<=size&&a[lchild]>a[max]){max=lchild;}  if(rchild<=size&&a[rchild]>a[max]){max=rchild;} if(max!=i){swap(a[i],a[max]);HeapAdjust(a,max,size); //避免调整之后以max为父节点的子树不是堆 }} }  void BuildHeap(int *a,int size) //建立堆 { int i; for(i=size/2;i>=1;i--) //非叶节点最大序号值为size/2 {HeapAdjust(a,i,size); } }   void HeapSort(int *a,int size) //堆排序 { int i;BuildHeap(a,size); for(i=size;i>=1;i--){ //cout<<a[1]<<" ";swap(a[1],a[i]); //交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面  //BuildHeap(a,i-1); //将余下元素重新建立为大顶堆 HeapAdjust(a,1,i-1); //重新调整堆顶节点成为大顶堆}}   int main(int argc, char *argv[]){ //int a[]={0,16,20,3,11,17,8}; int a[100]; int size; while(scanf("%d",&size)==1&&size>0){ int i; for(i=1;i<=size;i++)cin>>a[i];HeapSort(a,size); for(i=1;i<=size;i++)cout<<a[i]<<" ";cout<<endl;} return 0;}
 
 
 

归并排序是利用递归和分而治之的技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列,归并排序包括两个步骤,分别为:

1)划分子表

2)合并半子表

首先我们来讨论归并算法,归并算法将一系列数据放到一个向量中,索引范围为[first,last],这个序列由两个排好序的子表构成,以索引终点(mid)为分界线,以下面一个序列为例

7,10,19,25,12,17,21,30,48

这样的一个序列中,分为两个子序列 7,10,19,25  和 12,17,21,30,48,如下图所示:

image

再使用归并算法的时候的步骤如下:

第一步:比较v[indexA]=7和v[indexB]=12,将较小的v[indexA]取出来放到临时向量tempArray中,然后indexA加1

image

 

第二步:比较v[indexA]=10和v[indexB]=12,将较小的10放到临时变量tempArray中,然后indexA++;

image

第三步:比较v[indexA]=19与v[indexB]=12,将较小的12存放到临时变量tempArray中,然后indexB++;

image

第四步到第七步:按照以上规则,进行比对和存储,得到如下结果:

image

最后一步:将子表b中剩余项添加到临时向量tempArray中

image

然后将临时变量中的值按照索引位置,拷贝回向量v中,就完成了对向量v的归并排序

算法函数为:

public void Merger(int[] v, int first, int mid, int last)                Queue<int> tempV = new Queue<int>();           int indexA, indexB;           //设置indexA,并扫描subArray1 [first,mid]           //设置indexB,并扫描subArray2 [mid,last]           indexA = first;           indexB = mid;           //在没有比较完两个子标的情况下,比较 v[indexA]和v[indexB]           //将其中小的放到临时变量tempV中           while (indexA < mid && indexB < last)                        if (v[indexA] < v[indexB])                                tempV.Enqueue(v[indexA]);                   indexA++;                            else                                tempV.Enqueue(v[indexB]);                   indexB++;                                 //复制没有比较完子表中的元素           while (indexA < mid)                        tempV.Enqueue(v[indexA]);               indexA++;                    while (indexB < last)                        tempV.Enqueue(v[indexB]);               indexB++;                    int index = 0;           while (tempV.Count > 0)                        v[first+index] = tempV.Dequeue();               index++;                }

 

实现归并排序;归并排序算法分为两步,第一步:先将原来的数据表分成排好序的子表,然后调用 Merger  对子表进行归并,使之成为有序表,例如有如下向量:

25,10,7,19,3,48,12,17,56,30,21

对此序列进行归并排序的步骤为:

image

归并算法函数为

public void MergerSort(int[] v, int first, int last)                if (first + 1 < last)                        int mid = (first + last) / 2;               MergerSort(v, first, mid);               MergerSort(v, mid, last);               Merger(v, first, mid, last);                }

 

归并算法的划分子表和归并子表与原数据序列次序无关,因此算法的最坏情况,最坏情况和平均情况时间复杂度是一样的

下面是归并算法的函数调用图

image

 

 

 

算法描述  归并操作的工作原理如下:

  申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

  设定两个指针,最初位置分别为两个已经排序序列的起始位置

  比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

  重复步骤3直到某一指针达到序列尾

  将另一序列剩下的所有元素直接复制到合并序列尾

复杂度

  时间O(nlogn)

  空间O(n)

  与快速排序类似。

与快速排序的比较

  归并排序是稳定的排序.即相等的元素的顺序不会改变.如输入记录 1(1) 3(2) 2(3) 2(4) 5(5) (括号中是记录的关键字)时输出的 1(1) 2(3) 2(4) 3(2) 5(5) 中的2 和 2 是按输入的顺序.这对要排序数据包含多个信息而要按其中的某一个信息排序,要求其它信息尽量按输入的顺序排列时很重要.这也是它比快速排序优势的地方.

用途

1、排序

  (速度仅次于快速排序,但较稳定)

2、求逆序对数

  具体思路是,在归并的过程中计算每个小区间的逆序对数,进而计算出大区间的逆序对数(也可以用树状数组来求解)

C语言:

  输入参数中,需要排序的数组为array[],起始索引为first,终止索引为last。调用完成后,array[]中从first到last处于升序排列。

  void MergeSort(int array[], int first, int last)

  {

  int mid = 0;

  if(first<last)

  {

  mid = (first+last)/2;

  MergeSort(array, first, mid);

  MergeSort(array, mid+1,last);

  Merge(array,first,mid,last);

  }

  }

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
原创粉丝点击