1.比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?
顺序表中的数据存储在连续的存储空间内,所以查找的效率更高,但是插入和删除需要移动大量的数据,效率较低,适用于存储经常查找但很少插入和删除的数据;
链表因为存储在不连续的存储空间内,所以查找起来效率较低,但是插入或删除数据不需要移动其他数据,故插入和删除操作效率较高。适用于存储经常插入和删除而很少查找的数据。
2.从尾到头打印单链表
使用递归很容易就能实现逆序打印,代码如下:
void tail_to_head(ListNode* pList)
{
if(pList==NULL)
{
printf("%s",pList);
return;
}
else
tail_to_head(pList->next );
printf("->%d",pList->data );
}
用此函数虽然能够实现从尾到头打印单链表的目的,但是打印出来的结果最后面没有换行符,如果我们在从尾到头打印单链表的同时还需要打印其他东西,就会导致屏幕输出的结果相当混乱,最后我想出来的解决办法再写一个函数,在此函数中调用上面的那个函数,然后输出换行符。代码如下:
void tail_to_headPrintList(ListNode* pList)
{
tail_to_head(pList);
printf("\n");
}
3.删除一个无头单链表的非尾节点
此函数与Erase函数(删除指定节点)的不同之处是此函数没有给出单链表的起始位置指针,而只给出了需要删除的节点的指针pos,我们知道,要删除pos,不仅要释放pos所指向的空间,而且要让pos的前一个节点的next指针指向pos的下一个节点,但是此函数只给出了pos指针,我们没有办法找到pos的前一个节点,就无法直接删除pos,但是通过pos,我们可以删除pos的后一个节点,我们只需将pos后一个节点的内容挪动到pos指向的节点里,再删除pos的下一个节点,就相当于删除了pos。由于此函数在操作过程中要删除pos的下一个节点,所以pos不能为尾节点。
代码如下:
void Non_tail_Erase(ListNode* pos)
{
ListNode* ptmp;
assert(pos&&(pos->next ));
pos->data =pos->next->data ;
ptmp=pos->next;
pos->next =pos->next ->next ;
free(ptmp);
}
4.在无头单链表的一个节点前插入一个节点
和第三题一样,我们无法直接在pos的前面插入一个节点,但是我们可以在pos的后面插入一个节点,然后把pos的数据挪过去,然后在pos原来的位置上写入新的数据就好了。代码如下:
void Insert2(ListNode* pos, DataType x)
{
ListNode* p=malloc(sizeof(ListNode));
p->data =pos->data ;
p->next =pos->next ;
pos->data =x;
pos->next =p;
}
5.单链表实现约瑟夫环
约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围在一张圆桌周围,以编号为k的人开始报数数到m的那个人出列,他的下一个人有从1开始报数,数到m的人又出列,直至圆桌周围的人全部处理完。我们让单链表最后一个节点指向第一个节点,就可以模拟实现约瑟夫环问题了。代码如下:
ListNode* JosephCircle(ListNode* pList,int k,int m)
{
int i;
assert(pList);
for(i=1;i<k;i++)
pList=pList->next ;
//使pList向后挪动k-1个节点,指向第k个节点
while(1)
{
ListNode* ptmp;
if(pList==pList->next )
return pList;//只有一个节点时,返回这个节点的指针
for(i=1;i<m;i++)
pList=pList->next ;
//使pList向后挪动m-1个节点,指向报数为m的节点
ptmp=pList;//使ptmp指向报数为m的节点
while(pList->next !=ptmp)
pList=pList->next ;//循环一圈,使pList指向ptmp的前一个节点
pList->next =ptmp->next ;
//使报数为m的前一个节点指向m的后一个节点
free(ptmp);//删除报数为m的节点
pList=pList->next ;
//使pList指向m的下一个节点,作为下一轮循环的第一个节点
}
}
6.逆置/反转单链表
若单链表为空或只有一个节点,直接返回,若单链表含有多个节点:
1、将第一个节点的next指针指向NULL
2、将以后每个节点的next指针指向前一个节点
代码如下:
void ReverseList(ListNode** ppList)
{
ListNode* pnext;
ListNode* ptmp;
assert(ppList);
if((*ppList==NULL)||((*ppList)->next ==NULL))
return;//链表为空或只有一个节点,直接返回
pnext=(*ppList)->next ;
//*ppList指向第一个节点,pnext指向第二个节点
(*ppList)->next =NULL;//使第一个节点的next指向NULL
while(pnext)//使以后每个节点的next指向前一个节点
{
ptmp=pnext->next;//保存第三个节点位置
pnext->next =*ppList;//使第二个节点next指向第一个节点
*ppList=pnext;//*ppList挪动到第二个节点
pnext=ptmp;//pnext挪动到第三个节点
}
}
7.单链表排序(冒泡排序&快速排序)
在这里我只写了从小到大的排序,从大到小方法是一样的。
冒泡排序从第一个节点开始处理,比较第一个和第二个的值,如果第一个节点大于第二个节点,交换他们的值,然后处理第二个节点和第三个节点,知道处理完倒数第二个节点和最后一个节点时,所有数据中最大的值就到了最后一个节点,然后开始进行第二轮循环,由于最后一个节点已经是最大的值了,所以不用参加第二轮循环。
代码如下:
void BubbleSortList(ListNode* pList)
{
ListNode* p1=pList;
ListNode* p2=NULL;
if(NULL==pList)
return;
while(p1->next !=p2)
{
while(p1->next !=p2)
{
if((p1->data )>(p1->next ->data ))
{
DataType tmp=p1->data ;
p1->data =p1->next ->data ;
p1->next ->data =tmp;
//如果p1节点的值大于下一个节点的值,交换它们的位置
}
p1=p1->next ;//p1指向下一个节点
}/*循环结束时,最大的一个数被移动到了此次循环的
最后一个节点,p1指向p2前一个节点*/
p2=p1;//p2向前挪动一个节点
p1=pList;//p1重新指向第一个节点
}
}
快速排序是选取序列中的一个数作为基准数,把比它小的数都放在它左边,比它大的数都放在它右边,然后再对这两部分依次进行快速排序,直至每部分只有一个元素。
代码如下:
void QuickSortList(ListNode* pList)
{
if((NULL==pList)||(NULL==pList->next ))
return;//如果链表为空或只有一个节点,直接返回
else
{
ListNode* pkey=pList;//将第一个节点的值设为基准数
ListNode* p=pList->next;
while(p)//从第二个节点开始遍历
{
if(p->data<pkey->data)
{//如果p的值小于关键字,将它放在基准数的左边
DataType tmp=pkey->data;
pkey->data =p->data;//p的值放到pkey
p->data =pkey->next->data;//pkey后一个节点的值放到p
pkey->next->data =tmp;//pkey的值往后挪动一格
pkey=pkey->next;//pkey指向跟随pkey的值往后移动一格,始终指向关键字
}
p=p->next;
}
//循环结束后pkey左边的值都小于基准数,右边的值都大于基准数
p=pkey->next;
pkey->next=NULL;//通过将pkey的next指针置为空将链表拆为两部分
QuickSortList(pList);
QuickSortList(p);//对两部分分别进行快速排序
pkey->next=p;//重新将链表拼在一起
}
}
8.合并两个有序链表,合并后依然有序
ListNode* MergeList(ListNode* p1,ListNode* p2)
{
ListNode* pList;
if(NULL==p1)//p1为空返回p2
return p2;
if(NULL==p2)//p2为空返回p1
return p1;
if(p1->data<p2->data)
{
pList=BuyNode(p1->data);
p1=p1->next;
}
else
{
pList=BuyNode(p2->data);
p2=p2->next;
}
while(p1&&p2)
{
if(p1->data<p2->data)
{
PushBack(&pList,p1->data);
p1=p1->next;
}
else
{
PushBack(&pList,p2->data);
p2=p2->next;
}
}
//出此循环后,p1和p2至少有一个指向空
while(p1)
{
PushBack(&pList,p1->data);
p1=p1->next;
}
while(p2)
{
PushBack(&pList,p2->data);
p2=p2->next;
}
return pList;
}
9.查找单链表的中间节点,要求只能遍历一次链表
此题可用快慢指针解决,即创建两个指针一起玩后走,一个每次移动一个节点,一个每次移动两个节点,这样快指针到达链表尾时,慢指针刚好指向链表的中间节点。
代码如下:
ListNode* FindMidList(ListNode* pList)
{
ListNode* pfast=pList;
if(NULL==pList)
return NULL;//如果单链表为空,返回NULL
while(pfast&&pfast->next)
{
pList=pList->next;
pfast=pfast->next->next;
}
return pList;
}
10.查找单链表的倒数第k个节点,要求只能遍历一次链表
创建两个指针,一个先往后移动k个节点,然后一起往后移动,先走的到达NULL时,后面那个刚好指向倒数第k个。
代码如下:
ListNode* InverseList(ListNode* pList,int k)
{
ListNode* ptail=pList;
while(k--)
{
ptail=ptail->next;
}
while(ptail)
{
pList=pList->next;
ptail=ptail->next;
}
return pList;
}