一、关于指针的思考
指针的基本概念:
int a[100];
int *p;
p=a;
以上代码有一些基本的等价,需要铭记。
p等价于&a[0]等价于a
*p等价于a[0]等价于*a
*(p+1)等价于p[1]等价于a[1]等价于*(a+1)
但是,如果对数组名取址呢?
int a[]={1,2,3};
int *p;
int **q;
p=a;
q=&a;
printf("%x\n",p);
printf("%x\n",q);
这时候会发生错误,
error C2440: '=' : cannot convert from 'int (*)[3]' to 'int **'
printf("sizeof(p)=%d\n",sizeof(p));
printf("sizeof(q)=%d\n",sizeof(q));
printf("sizeof(a)=%d\n",sizeof(a));
printf("%x\n",a);
printf("%x\n",&a);
我们加上着三句,看看三个指针占用的内存大小,发现是
大小分别是4,4,12。那就意味着,&a与&a+1的距离是3个int单位(12 bytes)。
同时可以看到,a与&a的地址是一样的。
所以我们可以知道,虽然对a取&了,但是并不是传统意义上取a值所在的内存地址。
同时我们要清楚,&a的数据类型是 int (*)[3],占用12个bytes。
所以int **q; q=&a会报错。
怎么解决这个问题呢?将q的数据类型声明为int (*q)[3];
或者int *q; q=(int *)&a;
但是如果想用两重指针,是不能实现的。
二、金字塔路径和
这个是一家大外企,名声很大,招人很少,给的米很多。
我笔的是FPGA研发岗。问的是C语言问题。
这个是金字塔状结构,从顶部开始出发,沿着红色线出发,到达底部。
求路径和最小值。
(1)存储结构?
刚开始我想用二叉树。但是如果用二叉树,第5层就得用16个节点存储,会有大量的冗余。
其实,用数组就可以了。一个5x5的数组就搞定了。
(2)算法
如果允许修改数组内容,从上往下走一遍,每个节点从前两个节点中选择较小的一个相加就和,到底的时候,就会出现一排数值,选择就小的一个就是路径的最小和。
#include <stdio.h>
#include <stdlib.h>
void print(int (*p)[5],int N)
{
int i,j;
for(i=0;i<N;i++)
{
for(j=0;j<=i;j++)
{
printf(" %d",p[i][j]);
}
printf("\n");
}
}
void process(int (*a)[5],int N)
{
int i,j;
for(i=1;i<N;i++)
{
for(j=0;j<=i;j++)
{
if(j==0)
a[i][j]+=a[i-1][j];
else if(i==j)
a[i][j]+=a[i-1][j-1];
else if(a[i-1][j-1]>a[i-1][j])
a[i][j]+=a[i-1][j];
else
a[i][j]+=a[i-1][j-1];
}
}
printf("\n");
}
int main()
{
int a[5][5]={{1,},{3,2},{2,6,8},{6,7,4,2},{1,6,4,3,5}};
int i;
int min;
print(a,5);
process(a,5);
print(a,5);
for(i=1,min=a[4][0];i<5;i++)
if(a[4][i]<min)
min=a[4][i];
printf("the minimum number gained is %d\n",min);
}
三、链表翻转
一个单向链表,将其翻转。
比如1->2->3的结果,翻转就是3->2->1。
这个是一家台企和这个美国大公司共同问的一个问题,可见这题很普遍啊。
台企主要招去做手机底层驱动的软件开发,美企是做FPGA的。
node *init_link()
{
int i;
node *head,*p;
head=(node *)malloc(sizeof(node));
head->data=0;
p=head;
for(i=1;i<N;i++)
{
p=p->next;
p=(node *)malloc(sizeof(node));
p->data=i;
}
p->next=NULL;
return head;
}
谁能告诉,上面的这段单向链表的初始化出了什么问题?
这个是很多人容易犯的错误。下一个节点如果没有申请内存的话,将指针移到下一个节点,前节点是找不到的。
以下是我些的完全代码。
#include <stdio.h>
#include <stdlib.h>
#define N 10
typedef struct NODE{
int data;
struct NODE *next;
}node;
node *init_link()
{
int i;
node *head,*p;
head=(node *)malloc(sizeof(node));
head->data=0;
p=head;
for(i=1;i<N;i++)
{
p->next=(node *)malloc(sizeof(node));
p=p->next;
p->data=i;
}
p->next=NULL;
return head;
}
void print_link(node *head)
{
node *p;
p=head;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
void reverse_link(node **head)
{
node *p;
node *front,*back;
p=*head;
front=NULL;
while(p!=NULL)
{
back=p->next;
p->next=front;
front=p;
p=back;
}
*head=front;
}
int main()
{
node *head;
head=init_link();
print_link(head);
reverse_link(&head);
print_link(head);
return 0;
}
上面代码里面的链表初始化长度N考试是不知道的,笔试者应该只需要写reverse_link这个函数。整个题的难度系数很小,记得传入reverse_link函数的时候对链表头节点取地址就OK了。因为一般菜鸟不会想到传地址,比如我。
四、查找单向链表中倒数某个节点的值
假设这里有个单向链表,长度不详。想找到倒数第m个节点。怎么办?
菜鸟上场喽。这题?简单!
(1)、扫链表一遍,复制到数组里,然后看倒数第m个节点。大爷你想找哪个节点找哪个!
做算法,一定要假设几个极端情况。如果这个链表很长很长,存着空空老师几十个G的电影,你就不会傻帽地想申请新内存了。
(2)、把单向链表为双向。
数据结构不许改。
(3)、扫两遍,第一遍知道链表长度N,第二遍找第N-m个元素。
大哥,这傻子都能想到的solution。因为同一,如果这个玩意儿很大,大部分放硬盘里,重复读两遍,需要很多IO操作。这个也是不能接受的。虽然时间复杂度都是O(n)。
(4)、构造一个FIFO,长度m。读一个节点,扔FIFO里面。当读完单向链表时,FIFO里面出来的就是需要的那个节点,如果FIFO里面没出来东西,说明单向链表长度不足m。
如果m=1000000000000000000000000呢?你确定能申请到这个内存?当然,这个思路已经比较靠近正确答案了。
所有的链表问题,都是指针问题。一个指针不能解决的问题,用两个!一个指针指着当前读取链表,一个滞后m个节点。前面的指针读取到尾节点,滞后的指针所指即为需要的节点。代码如下。
#include <stdio.h>
#include <stdlib.h>
#define N 10
typedef struct NODE{
int data;
struct NODE *next;
}node;
node *init_link()
{
int i;
node *head,*p;
head=(node *)malloc(sizeof(node));
head->data=0;
p=head;
for(i=1;i<N;i++)
{
p->next=(node *)malloc(sizeof(node));
p=p->next;
p->data=i;
}
p->next=NULL;
return head;
}
void print_link(node *head)
{
node *p;
p=head;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
node *search_link(node *head,int m)
{
node *front,*back;
int i;
front=head;
back=head;
for(i=0;i<m;i++)
{
if(front->next==NULL)
return NULL;
else
front=front->next;
}
while(front->next!=NULL)
{
front=front->next;
back=back->next;
}
return back;
}
int main()
{
node *head,*result;
int m=1;
head=init_link();
print_link(head);
result=search_link(head,m);
printf("result is %d\n",result==NULL?-1:result->data);
return 0;
}
四、寻找单向链表中的闭合回路
单向链表的解决方法中,经常需要两个指针,比如这个寻找闭合回路的问题。
闭合回路并不一定是循环链表,它也许只是部分循环。
怎么寻找呢?
比较巧妙的就是构造两个指针,一个步长为一,一个为二,跑的快的和跑的慢的会重合。OK,就是有回路了。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 100
typedef struct NODE{
int data;
struct NODE *next;
}node;
node *init_link()
{
int i;
node *head,*p;
node *record;
int record_node;
srand((unsigned)time(NULL));
record_node=rand()%N;
printf("loop point number is %d\n",record_node);
head=(node *)malloc(sizeof(node));
head->data=rand()%1000;
p=head;
for(i=1;i<N;i++)
{
p->next=(node *)malloc(sizeof(node));
p=p->next;
p->data=rand()%100;
//create loop
if(i==record_node)
record=p;
}
p->next=record;
return head;
}
void print_link(node *head)
{
node *p;
p=head;
int i=0;
while(p!=NULL && i<2*N)
{
printf("%d ",p->data);
p=p->next;
i++;
if(i==N)printf("###");
}
printf("\n");
}
int check_loop(node * head)
{
node *fast,*slow;
fast=slow=head;
while(fast->next!=NULL && fast->next->next!=NULL)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
{
return 1;
}
}
return -1;
}
int main()
{
node *head;
head=init_link();
print_link(head);
printf("Is there a loop? %s\n",check_loop(head)?"yes":"no");
return 0;
}
链表的指针就这点小伎俩,很easy吧?
五、寻找单向链表中的中间节点
这个和三里面提到的倒数第m个节点是不是有异曲同工之妙?
当然是采用两个节点,用四里面提到的调节步长。一个跳动一,一个跳动二。跳的快的到来tail,是不是跳的慢的就到中间了?
那有个傻帽的技术面出,寻找三分之一节点呢、三分之二呢?是不是都一个套路?
给出代码前,给些约定。
五个节点,中间节点是第三个,这个没问题。如果有六个呢?你就需要问技术面的人了,是需要给出第三个还是第四个?还是两个都给?当然,details还是需要关注的。这样可以传达出我们对细节的care。这是优秀程序员应该具有的品质。
下面基于6个节点,给第三个算中间节点的方法。当然给出第四个,也就一个next的问题。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 12
typedef struct NODE{
int data;
struct NODE *next;
}node;
node *init_link()
{
int i;
node *head,*p;
srand((unsigned)time(NULL));
head=(node *)malloc(sizeof(node));
head->data=rand()%1000;
p=head;
for(i=1;i<N;i++)
{
p->next=(node *)malloc(sizeof(node));
p=p->next;
p->data=rand()%100;
}
p->next=NULL;
return head;
}
void print_link(node *head)
{
node *p;
p=head;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
node* search_middle(node * head)
{
node *fast,*slow;
fast=slow=head;
while(fast->next!=NULL && fast->next->next!=NULL)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
int main()
{
node *head;
node *middle;
head=init_link();
print_link(head);
middle=search_middle(head);
printf("middle node's value is %d\n",middle->data);
return 0;
}
Conclusion
链表要善于用指针,一个不行用两个。步长也可以调。时间复制度控制在O(n),遍历一遍。同时只是申请指针空间,空间复杂度极低。