在一些公司面试中,数据结构与算法一般都是大公司都会考的题目,而小公司考得很少。考试题目一般集中排序算法与时间复杂度、链表结构的应用。
一、排序算法与复杂度
常用排序算法的时间复杂度和空间复杂度
排序法 | 最差时间分析 | 平均时间复杂度 | 稳定度 | 空间复杂度 |
冒泡排序 | O(n2) | O(n2) | 稳定 | O(1) |
快速排序 | O(n2) | O(n*log2n) | 不稳定 | O(log2n)~O(n) |
选择排序 | O(n2) | O(n2) | 稳定 | O(1) |
二叉树排序 | O(n2) | O(n*log2n) | 不一顶 | O(n) |
插入排序 | O(n2) | O(n2) | 稳定 | O(1) |
堆排序 | O(n*log2n) | O(n*log2n) | 不稳定 | O(1) |
希尔排序 | O | O | 不稳定 | O(1) |
1、时间复杂度
(1)时间频度一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。
(2)时间复杂度在刚才提到的时间频度中,n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。但有时我们想知道它变化时呈现什么规律。为此,我们引入时间复杂度概念。一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
在各种不同算法中,若算法中语句执行次数为一个常数,则时间复杂度为O(1),另外,在时间频度不相同时,时间复杂度有可能相同,如T(n)=n2+3n+4与T(n)=4n2+2n+1它们的频度不同,但时间复杂度相同,都为O(n2)。按数量级递增排列,常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n), 线性对数阶O(nlog2n),平方阶O(n2),立方阶O(n3),..., k次方阶O(nk),指数阶O(2n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。 2、空间复杂度与时间复杂度类似,空间复杂度是指算法在计算机内执行时所需存储空间的度量。记作: S(n)=O(f(n)) 我们一般所讨论的是除正常占用内存开销外的辅助存储单元规模。讨论方法与时间复杂度类似,不再赘述。
(3)渐进时间复杂度评价算法时间性能 主要用算法时间复杂度的数量级(即算法的渐近时间复杂度)评价一个算法的时间性能。
2、类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)S(n)定义为该算法所耗费的存储空间,它也是问题规模n的函数。渐近空间复杂度也常常简称为空间复杂度。
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。一个算法在计算机存储器上所占用的存储空间,包括存储算法本身所占用的存储空间,算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储空间这三个方面。算法的输入输出数据所占用的存储空间是由要解决的问题决定的,是通过参数表由调用函数传递而来的,它不随本算法的不同而改变。存储算法本身所占用的存储空间与算法书写的长短成正比,要压缩这方面的存储空间,就必须编写出较短的算法。算法在运行过程中临时占用的存储空间随算法的不同而异,有的算法只需要占用少量的临时工作单元,而且不随问题规模的大小而改变,我们称这种算法是“就地/"进行的,是节省存储的算法,如这一节介绍过的几个算法都是如此;有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元,例如将在第九章介绍的快速排序和归并排序算法就属于这种情况。
如当一个算法的空间复杂度为一个常量,即不随被处理数据量n的大小而改变时,可表示为O(1);当一个算法的空间复杂度与以2为底的n的对数成正比时,可表示为0(10g2n);当一个算法的空I司复杂度与n成线性比例关系时,可表示为0(n).若形参为数组,则只需要为它分配一个存储由实参传送来的一个地址指针的空间,即一个机器字长空间;若形参为引用方式,则也只需要为其分配存储一个地址的空间,用它来存储对应实参变量的地址,以便由系统自动引用实参变量。
各种算法汇总:
/*
================================================
功能:选择排序
输入:数组名称(也就是数组首地址)、数组中元素个数
================================================
*/
/*
====================================================
算法思想简单描述:
在要排序的一组数中,选出最小的一个数与第一个位置的数交换;
然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环
到倒数第二个数和最后一个数比较为止。
选择排序是不稳定的。算法复杂度O(n2)--[n的平方]
=====================================================
*/
void select_sort(int *x, int n)
{
int i, j, min, t;
for (i=0; i<n-1; i++) /*要选择的次数:0~n-2共n-1次*/
{
min = i; /*假设当前下标为i的数最小,比较后再调整*/
for (j=i+1; j<n; j++)/*循环找出最小的数的下标是哪个*/
{
if (*(x+j) < *(x+min))
{
min = j; /*如果后面的数比前面的小,则记下它的下标*/
}
}
if (min != i) /*如果min在循环中改变了,就需要交换数据*/
{
t = *(x+i);
*(x+i) = *(x+min);
*(x+min) = t;
}
}
}
/*
================================================
功能:直接插入排序
输入:数组名称(也就是数组首地址)、数组中元素个数
================================================
*/
/*
====================================================
算法思想简单描述:
在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排
好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数
也是排好顺序的。如此反复循环,直到全部排好顺序。
直接插入排序是稳定的。算法时间复杂度O(n2)--[n的平方]
=====================================================
*/
void insert_sort(int *x, int n)
{
int i, j, t;
for (i=1; i<n; i++) /*要选择的次数:1~n-1共n-1次*/
{
/*
暂存下标为i的数。注意:下标从1开始,原因就是开始时
第一个数即下标为0的数,前面没有任何数,单单一个,认为
它是排好顺序的。
*/
t=*(x+i);
for (j=i-1; j>=0 && t<*(x+j); j--) /*注意:j=i-1,j--,这里就是下标为i的数,在它前面有序列中找插入位置。*/
{
*(x+j+1) = *(x+j); /*如果满足条件就往后挪。最坏的情况就是t比下标为0的数都小,它要放在最前面,j==-1,退出循环*/
}
*(x+j+1) = t; /*找到下标为i的数的放置位置*/
}
}
/*
================================================
功能:冒泡排序
输入:数组名称(也就是数组首地址)、数组中元素个数
================================================
*/
/*
====================================================
算法思想简单描述:
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上
而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较
小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要
求相反时,就将它们互换。
下面是一种改进的冒泡算法,它记录了每一遍扫描后最后下沉数的
位置k,这样可以减少外层循环扫描的次数。
冒泡排序是稳定的。算法时间复杂度O(n2)--[n的平方]
=====================================================
*/
void bubble_sort(int *x, int n)
{
int j, k, h, t;
for (h=n-1; h>0; h=k) /*循环到没有比较范围*/
{
for (j=0, k=0; j<h; j++) /*每次预置k=0,循环扫描后更新k*/
{
if (*(x+j) > *(x+j+1)) /*大的放在后面,小的放到前面*/
{
t = *(x+j);
*(x+j) = *(x+j+1);
*(x+j+1) = t; /*完成交换*/
k = j; /*保存最后下沉的位置。这样k后面的都是排序排好了的。*/
}
}
}
}
/*
================================================
功能:希尔排序
输入:数组名称(也就是数组首地址)、数组中元素个数
================================================
*/
/*
====================================================
算法思想简单描述:
在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,
并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为
增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除
多个元素交换。D.L.shell于1959年在以他名字命名的排序算法中实现
了这一思想。算法先将要排序的一组数按某个增量d分成若干组,每组中
记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量
对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成
一组,排序完成。
下面的函数是一个希尔排序算法的一个实现,初次取序列的一半为增量,
以后每次减半,直到增量为1。
希尔排序是不稳定的。
=====================================================
*/
void shell_sort(int *x, int n)
{
int h, j, k, t;
for (h=n/2; h>0; h=h/2) /*控制增量*/
{
for (j=h; j<n; j++) /*这个实际上就是上面的直接插入排序*/
{
t = *(x+j);
for (k=j-h; (k>=0 && t<*(x+k)); k-=h)
{
*(x+k+h) = *(x+k);
}
*(x+k+h) = t;
}
}
}
/*
================================================
功能:快速排序
输入:数组名称(也就是数组首地址)、数组中起止元素的下标
================================================
*/
/*
====================================================
算法思想简单描述:
快速排序是对冒泡排序的一种本质改进。它的基本思想是通过一趟
扫描后,使得排序序列的长度能大幅度地减少。在冒泡排序中,一次
扫描只能确保最大数值的数移到正确位置,而待排序序列的长度可能只
减少1。快速排序通过一趟扫描,就能确保某个数(以它为基准点吧)
的左边各数都比它小,右边各数都比它大。然后又用同样的方法处理
它左右两边的数,直到基准点的左右只有一个元素为止。它是由
C.A.R.Hoare于1962年提出的。
显然快速排序可以用递归实现,当然也可以用栈化解递归实现。下面的
函数是用递归实现的,有兴趣的朋友可以改成非递归的。
快速排序是不稳定的。最理想情况算法时间复杂度O(nlog2n),最坏O(n2)
=====================================================
*/
void quick_sort(int *x, int low, int high)
{
int i, j, t;
if (low < high) /*要排序的元素起止下标,保证小的放在左边,大的放在右边。这里以下标为low的元素为基准点*/
{
i = low;
j = high;
t = *(x+low); /*暂存基准点的数*/
while (i<j) /*循环扫描*/
{
while (i<j && *(x+j)>t) /*在右边的只要比基准点大仍放在右边*/
{
j--; /*前移一个位置*/
}
if (i<j)
{
*(x+i) = *(x+j); /*上面的循环退出:即出现比基准点小的数,替换基准点的数*/
i++; /*后移一个位置,并以此为基准点*/
}
while (i<j && *(x+i)<=t) /*在左边的只要小于等于基准点仍放在左边*/
{
i++; /*后移一个位置*/
}
if (i<j)
{
*(x+j) = *(x+i); /*上面的循环退出:即出现比基准点大的数,放到右边*/
j--; /*前移一个位置*/
}
}
*(x+i) = t; /*一遍扫描完后,放到适当位置*/
quick_sort(x,low,i-1); /*对基准点左边的数再执行快速排序*/
quick_sort(x,i+1,high); /*对基准点右边的数再执行快速排序*/
}
}
/*
================================================
功能:堆排序
输入:数组名称(也就是数组首地址)、数组中元素个数
================================================
*/
/*
====================================================
算法思想简单描述:
堆排序是一种树形选择排序,是对直接选择排序的有效改进。
堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当
满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)
时称之为堆。在这里只讨论满足前者条件的堆。
由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项。完全二叉树可以
很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。
初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储顺序,
使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点
交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点
的堆,并对它们作交换,最后得到有n个节点的有序序列。
从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素
交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数
实现排序的函数。
堆排序是不稳定的。算法时间复杂度O(nlog2n)。
*/
/*
功能:渗透建堆
输入:数组名称(也就是数组首地址)、参与建堆元素的个数、从第几个元素开始
*/
void sift(int *x, int n, int s)
{
int t, k, j;
t = *(x+s); /*暂存开始元素*/
k = s; /*开始元素下标*/
j = 2*k + 1; /*右子树元素下标*/
while (j<n)
{
if (j<n-1 && *(x+j) < *(x+j+1))/*判断是否满足堆的条件:满足就继续下一轮比较,否则调整。*/
{
j++;
}
if (t<*(x+j)) /*调整*/
{
*(x+k) = *(x+j);
k = j; /*调整后,开始元素也随之调整*/
j = 2*k + 1;
}
else /*没有需要调整了,已经是个堆了,退出循环。*/
{
break;
}
}
*(x+k) = t; /*开始元素放到它正确位置*/
}
/*
功能:堆排序
输入:数组名称(也就是数组首地址)、数组中元素个数
*/
void heap_sort(int *x, int n)
{
int i, k, t;
int *p;
for (i=n/2-1; i>=0; i--)
{
sift(x,n,i); /*初始建堆*/
}
for (k=n-1; k>=1; k--)
{
t = *(x+0); /*堆顶放到最后*/
*(x+0) = *(x+k);
*(x+k) = t;
sift(x,k,0); /*剩下的数再建堆*/
}
}
void main()
{
#define MAX 4
int *p, i, a[MAX];
/*录入测试数据*/
p = a;
printf("Input %d number for sorting :/n",MAX);
for (i=0; i<MAX; i++)
{
scanf("%d",p++);
}
printf("/n");
/*测试选择排序*/
p = a;
select_sort(p,MAX);
/**/
/*测试直接插入排序*/
/*
p = a;
insert_sort(p,MAX);
*/
/*测试冒泡排序*/
/*
p = a;
insert_sort(p,MAX);
*/
/*测试快速排序*/
/*
p = a;
quick_sort(p,0,MAX-1);
*/
/*测试堆排序*/
/*
p = a;
heap_sort(p,MAX);
*/
for (p=a, i=0; i<MAX; i++)
{
printf("%d ",*p++);
}
printf("/n");
system("pause");
}
二叉树排序算法
#include "stdio.h"
#include "stdlib.h"
#define null 0
#define n 100
typedef struct node
{
int key;
struct node *lchild,*rchild;
}bstnode;
typedef bstnode *bstree;
//插入元素
void insertbst(bstree *tptr,int key)
{
bstnode *f,*p=*tptr;
while(p)
{
f=p;
p=(key<=p->key)?p->lchild:p->rchild;
}
p=(bstnode *)malloc(sizeof(bstnode));
p->key=key;
p->lchild=p->rchild=null;
if(*tptr==null)
*tptr=p;
else
if (key<f->key)
f->lchild=p;
else
f->rchild=p;
return;
}
//创建二叉树
void createbst(bstree *t)
{
int key;
int j,i,m;
printf("input data end of 0:/n");
scanf("%d",&key);
while(key)
{
insertbst(t,key);
scanf("%d",&key);
}
return;
}
//遍历二叉树(分为前序、中序、后序)
void inorder(bstree t)
{
if (t)
{
inorder(t->lchild);
printf(" %d",t->key);
inorder(t->rchild);
}
return;
}
void main()
{
bstree t=null,q=null;
int k;
createbst(&t);
printf("/n bst data:");
inorder(t);
printf("/n");
return;
}
几种排序算法的比较和选择
1. 选取排序方法需要考虑的因素:
(1) 待排序的元素数目n;
(2) 元素本身信息量的大小;
(3) 关键字的结构及其分布情况;
(4) 语言工具的条件,辅助空间的大小等。
2. 小结:
(1) 若n较小(n <= 50),则可以采用直接插入排序或直接选择排序。由于直接插入排序所需的记录移动操作较直接选择排序多,因而当记录本身信息量较大时,用直接选择排序较好。
(2) 若文件的初始状态已按关键字基本有序,则选用直接插入或冒泡排序为宜。
(3) 若n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序。快速排序是目前基于比较的内部排序法中被认为是最好的方法。
(4) 在基于比较排序方法中,每次比较两个关键字的大小之后,仅仅出现两种可能的转移,因此可以用一棵二叉树来描述比较判定过程,由此可以证明:当文件的n个关键字随机分布时,任何借助于"比较"的排序算法,至少需要O(nlog2n)的时间。
(5) 当记录本身信息量较大时,为避免耗费大量时间移动记录,可以用链表作为存储结构。
二、链表操作
链表包括单向链表、双向链表、循环链表等。
头结点: 头结点是为了操作的统一、方便而设立的,放在第一元素结点之前,其数据域一般无意义(当然有些情况下也可存放链表的长度、用做监视哨等等),有头结点后,对在第一元素结点前插入结点和删除第一结点,其操作与对其它结点的操作统一了。而且无论链表是否为空,头指针均不为空。
头指针: 头指针指向链表的第一个节点,若链表有头结点则指向链表的头结点,头指针具有标识作用,故对头指针常冠以链表的名字。
首元结点也就是第一元素结点,它是头结点后边的第一个结点。
链表的创建和逆序:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
typedef struct List{
int data;
struct List *next;
}List;
//顺序创建链表,有头结点和头指针。
List *list_create(void)
{
struct List *head,*tail,*p;
int e;
head=(List *)malloc(sizeof(List));
tail=head;
printf("/nList Create,input numbers(end of 0):");
scanf("%d",&e);
while(e){
p=(List *)malloc(sizeof(List));
p->data=e;
tail->next=p;
tail=p;
scanf("%d",&e);}
tail->next=NULL;
return head;
}
//使链表逆序,需有头指针。如果有头结点,会把头结点也逆序了。
List *list_reverse(List *head)
{
List *p,*q,*r;
if(head != NULL)
{
p=head;
q=p->next;
}
while(q!=NULL)
{
r=q->next;
q->next=p;
p=q;
q=r;
}
head->next=NULL;
head=p;
return head;
}
void main(void)
{
struct List *head,*p;
int d;
head=list_create();
printf("/n");
for(p=head->next;p;p=p->next)
printf("--%d--",p->data);
head=list_reverse(head);
printf("/n");
for(p=head;p->next;p=p->next)
printf("--%d--",p->data);
}
转置的其他方法: 1. 堆栈
2. 数组
这两种方法时间和空间效率都不高。
3. 递归算法:
#include <stdio.h>
#include <malloc.h>
#define Malloc(x) (x *)malloc(sizeof(x));
typedef struct Lt {
char a;
struct Lt *next;
}List;
List* rev( List * head )
{
List *rHead;
if( !head )
{
return head;
}
else if( !head->next ) //只有一个结点
{
return head;
}
else
{
rHead = rev( head->next );
head->next->next = head;
head->next = NULL;
return rHead;
}
}
void output(List *h){
for(;h;h=h->next)
printf("%c ",h->a);
printf("/n");
}
int main()
{
List *h,*p;
int i;
h=Malloc(List);
p=h;
p->a='a';p->next=0;
for(i=0;i<5;i++){
p->next=Malloc(List);
p=p->next;
p->a='b'+i;p->next=0;
}
output(h);
h = rev(h);
output(h);
return 0;
}
三、相关面试题目
1、 反转一个链表。循环算法。
List reverse(List l) {
if(!l) return l;
list cur = l.next;
list pre = l;
list tmp;
pre.next = null;
while ( cur ) {
tmp = cur;
cur = cur.next;
tmp.next = pre
pre = tmp;
}
return tmp;
}
2、反转一个链表。递归算法。
List resverse(list l) {
if(!l || !l.next) return l;
List n = reverse(l.next);
l.next.next = l;
l.next=null;
}
return n;
}
3、广度优先遍历二叉树。
void BST(Tree t) {
Queue q = new Queue();
q.enque(t);
Tree t = q.deque();
while(t) {
System.out.println(t.value);
q.enque(t.left);
q.enque(t.right);
t = q.deque();
}
}
class Node {
Tree t;
Node next;
}
class Queue {
Node head;
Node tail;
public void enque(Tree t){
Node n = new Node();
n.t = t;
if(!tail){
tail = head = n;
} else {
tail.next = n;
tail = n;
}
}
public Tree deque() {
if (!head) {
return null;
} else {
Node n = head;
head = head.next;
return n.t;
}
}
4、输出一个字符串所有排列。注意有重复字符。
char[] p;
void perm(char s[], int i, int n){
int j;
char temp;
for(j=0;j<n;++j){
if(j!=0 && s[j]==s[j-1]);
elseif(s[j]!='@'){
p[i]=s[j];
s[j]='@';
if(i==n-1){
p[n]='/0';
printf("%s", p);
}else{
perm(s,i+1,n);
}
s[j]=p[i];
}
}
}
void main() {
char s[N];
sort(s);
perm(s,0,strlen(s));
}
5、输入一个字符串,输出长型整数。
long atol(char *str){
char *p = str;
long l=1;m=0;
if (*p=='-') {
l=-1;
++p;
}
while(isDigit(*p)){
m = m*10 + p;
++p;
}
if(!p) return m*l;
else return error;
}
6、判断一个链表是否有循环。
int isLoop(List l) {
if ( ! l) return - 1 ;
List s = l.next;
while (s && s != l) {
s = s.next;
}
if ( ! s) return - 1 ;
else reutrn 1 ;
}
int isLoop(List l){
if(!l) return 0;
p=l.next;
wihle(p!=l&&p!=null) {
l.next=l;
l=p;p=p.next;
}
if(p=l) return 1;
return 0;
}
实际上,在我的面试过程中,还问到了不破坏结构的其他算法。
我的答案是从链表头开始遍历,如果节点next指针指向自身,则循环存在;否则将next指针指向自身,遍历下一个节点。直至next指针为空,此时链表无循环。
http://ostermiller.org/find_loop_singly_linked_list.html
http://en.wikipedia.org/wiki/Floyd's_cycle-finding_algorithm
http://ninputer.cnblogs.com/">http://ninputer.cnblogs.com/
不是随便两个步长不同的指针都会在循环链表上相遇。一个充分条件是,两个遍历指针步长相差1。最好是1,2这两个步长,这样就可以进而求出循环的位置和链表的大小。
7、反转一个字符串。
void reverse( char * str) {
char tmp;
int len;
len = strlen(str);
for ( int i = 0 ;i < len / 2 ; ++ i) {
tmp = char [i];
str[i] = str[len - i - 1 ];
str[len - i - 1 ] = tmp;
}
}
8、实现strstr函数。
int strstr(char[] str, char[] par){
int i=0;
int j=0;
while(str[i] && str[j]){
if(str[i]==par[j]){
++i;
++j;
}else{
i=i-j+1;
j=0;
}
}
if(!str[j]) return i-strlen(par);
else return -1;
}
9、实现strcmp函数。
int strcmp(char* str1, char* str2){
while(*str1 && *str2 && *str1==*str2){
++str1;
++str2;
}
return *str1-*str2;
}
10、求整形中二进制1的位数
求一个整形中1的位数。
int f( int x) {
int n = 0 ;
while (x) {
++ n;
x &= x - 1 ;
}
return n;
}
11、汉诺塔问题。
void tower(n,x,y,z){
if(n==1) move(x,z);
else {
tower(n-1, x,z,y);
move(x,z);
tower(n-1, y,x,z);
}
}
12、汉诺塔最小步数
三柱汉诺塔最小步数。
int f3(n) {
if (f3[n]) return f3[n];
else {
if (n == 1 ) {
f3[n] = 1 ;
return 1 ;
}
f3[n] = 2 * f3(n - 1 ) + 1 ;
return f3[n];
}
}
四柱汉诺塔最小步数。
int f4(n){
if(f4[n]==0){
if(n==1) {
f4[1]==1;
return 1;
}
min=2*f4(1)+f3(n-1);
for(int i=2;i<n;++i){
u=2*f4(i)+f3(n-i);
if(u<min) min=u;
}
f4[n]=min;
return min;
} else return f4[n];
}
13、在一个链表中删除另一个链表中的元素。
void delete(List m, List n) {
if(!m || !n) return;
List pre = new List();
pre.next=m;
List a=m, b=n,head=pre;
while(a && b){
if(a.value < b.value) {
a=a.next;
pre=pre.next;
}else if(a.value > b.value){
b=b.next;
}else{
a=a.next;
pre.next=a;
}
}
m=head.next;
}
14、一个数组,下标从0到n,元素为从0到n的整数。判断其中是否有重复元素。
int hasDuplicate(int[] a, int n){
for(int i=0;i<n;++i){
while(a[i]!=i && a[i]!=-1){
if(a[a[i]]==-1) return 1;
a[i]=a[a[i]];
a[a[i]]=-1;
}
if(a[i]==i) {a[i]=-1;}
}
return 0;
}
15、判断一颗二叉树是否平衡。
int isB(Tree t){
if(!t) return 0;
int left=isB(t.left);
int right=isB(t.right);
if( left >=0 && right >=0 && left - right <= 1 || left -right >=-1)
return (left<right)? (right +1) : (left + 1);
else return -1;
}
16、返回一颗二叉树的深度。
int depth(Tree t){
if(!t) return 0;
else {
int a=depth(t.right);
int b=depth(t.left);
return (a>b)?(a+1):(b+1);
}
}
17、两个链表,一升一降。合并为一个升序链表。
List merge(List a, List d) {
List a1 = reverse(d);
List p = q = new List();
while ( a && a1 ) {
if (a.value < a1.value) {
p.next = a;
a = a.next;
} else {
p.next = a1;
a1 = a1.next;
}
p = p.next;
}
if (a) p.next = a;
elseif(a1) p.next = a1;
return q.next;
}
不知道这道题朋友们有什么更好的方法?
http://ninputer.cnblogs.com/">http://ninputer.cnblogs.com/
链表逆序本来就是O(N),所以这道题再烂的做法也不会超过O(N)。我觉得先逆序再归并不会比设计一个个巧妙的方法一次组成目标链表慢多少的
18、将长型转换为字符串。
char* ltoa(long l){
char[N] str;
int i=1,n=1;
while(!(l/i<10)){i*=10;++n}
char* str=(char*)malloc(n*sizeof(char));
int j=0;
while(l){
str[j++]=l/i;
l=l%i;
i/=10;
}
return str;
}
19、用一个数据结构实现
if (x == 0) y = a;
else y = b;
j[] = {a,b};
y=j[x];
20、在双向链表中删除指定元素。
void del(List head, List node){
List pre=new List();
pre.next = head;
List cur = head;
while(cur && cur!=node){
cur=cur.next;
pre=pre.next;
}
if(!cur) return;
List post = cur.next;
pre.next=cur.next;
post.last=cur.last;
return;
}
21、不重复地输出升序数组中的元素。
void outputUnique( char [] str, int n) {
if (n <= 0 ) return ;
elseif(n == 1 ) putchar(str[ 0 ]);
else {
int i = 0 ,j = 1 ;
putchar(str[ 0 ]);
while (j < n) {
if (str[j] !== str[i]) {
putchar(str[j]);
i = j;
}
++ j;
}
}
}
22、面试过程中我还遇到了下面几题:
1)、如何删除链表的倒数第m的元素?我的方法是先用pre指针从链表头开始步进m,新建pst节点next指针指向头节点,cur指针指向头节点,然后pre,cur,post三个指针一起步进,当pre指向链表结尾的时候cur指向倒数第m个元素,最后利用pst指针删除cur指向元素。
2)、如何判断一个字符串是对称的?如a,aa,aba。设置头尾指针同时向中间比较靠齐直至相遇。
3)、如何利用2函数找出一个字符串中的所有对称子串?以子串头指针和尾指针为循环变量设置两个嵌套的循环以找出所有子串,对每个子串应用2函数。
注:关于其他数据结构的了解与研究请参看《数据结构》书籍。
http://www.aybook.cn/book/24627.html
http://www.luocong.com/dsaanotes/index-Z-H-1.htm