您的位置:首页 > 职场人生

单链表常见面试题(一)

2017-07-07 16:17 274 查看
1.比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?

    顺序表:类似于数组结构。是连续存储的。在读取的时候比较快。但是在插入和删除时移动的数据量比较大,非常麻烦。顺序表在开辟空间时在满的时候重新申请开辟大的空间,会存在空间浪费。

    链表:是链式结构。对于插入和删除操作比较方便。在开辟空间时对单个节点申请空间,不会造成空间的浪费。

首先我们时间上来进行分析:

(1)对于顺序表。不论是静态的还是动态的,他们都是连续的存储空间,在读取上时间效率比较快,可以通过地址之间的运算来进行访问,但是在插入和删除操作会出现比较麻烦的负载操作。 

(2)对于链表,因为他是链式存储。在我们需要的时候才在堆上开辟空间,对于插入查找的方式比较便携。但是对于遍历的话需要多次的空间跳转。

顺序表多用于遍历操作频繁时使用,链表多用于在删除插入多的时候使用。

2.从尾到头打印单链表 

    这个问题我们使用递归的思想来解决。从头结点开始,在打印头结点数据前先打印它前一个节点数据,以此类推,首先打印最后一个节点数据,调用结束返回上一层。
void RePrintf(Node* pList)//2逆序打印
{
if (pList)
{
RePrintf(pList->next);  //递归调用
printf("%d->", pList->data);
}
}


3.删除一个无头单链表的非尾节点
 

   在这个问题上,要保证删除后前后节点依旧能够连接。但在单链表中,我们无法找到要删除的结点的前一个节点,但是我们可以找到下一个。因此交换当前要删除的结点和下一个节点的数据。再把前后链接关系写上,就可以来解决。



我们在这里本来是删除cur,这里用next替换删除。
void EraseNontail(Node**pList, Node* pos)//删除无头单链表的非尾节点
{
assert(pos);
assert(*pList);
Node* cur = pos->next;
pos->data=cur->data ;
pos->next = cur->next;
free(cur);
cur = NULL;
}


4.在无头单链表的一个节点前插入一个节点 

    在插入节点时,我们也要把插入后的前后链接关系写清楚。所以依旧用替换法。本来是插入到pos节点,但我们找到pos的next节点。交换数据。



void InsertNonHead(Node**pList, Node* pos,DataType x)//4无头单链表前插入
{
assert(pos);
assert(*pList);
Node* cur = BuyNode(x);
Node* next = pos->next;
DataType tmp = cur->data;
cur->data = pos->data;
pos->data = tmp;
pos->next = cur;
cur->next = next;
}


5.单链表实现约瑟夫环 

Node* JosephRing(Node* pList, int k)//5约瑟夫环
{
Node* cur = pList;
Node* next = NULL;
Node* tail = pList;
while (tail->next)
{
tail = tail->next;
}
tail->next = pList;
while (cur->next != cur)
{
int count = k;
while (--count)
{
cur = cur->next;
}
next = cur->next;
cur->data = next->data;
cur->next = next->next;
free(next);
next = NULL;
}
return cur;
}

6.逆置/反转单链表



void ReList(Node** pList)//6逆置反转单链表
{
if (*pList == NULL)
{
return 0;
}
Node* cur = *pList;
*pList = NULL;
while (cur)
{
Node* tmp = cur;
cur = cur->next;
PushFront(pList, tmp->data); //头插到pList中
}
}7单链表排序(冒泡排序&快速排序)



设置一个tail,用来表明已经有序的界限。每次把cur和next进行比较。若next->data 大于 cur->data,则交换两个值。继续走,直到遇到tail

void SortList(Node* pList)// 7冒泡排序
{
if (pList == NULL || pList->next == NULL)
{
return;
}
Node* tail = NULL;
while (tail != pList)//总体循环条件 一共排序多少次
{
Node* cur = pList;
Node* next = cur->next;
int flag = 0;
while (next != tail) //在每一次中的循环条件
{
if (cur->data > next->data)
{
int data = cur->data;
cur->data = next->data;
next->data = data;
flag = 1;
}
cur = next;
next = cur->next;
}
tail = next;
if (flag == 0) //用来优化冒泡排序,若flag值没有变化,则表明已经有序。直接return
{
return;
}
}
}

8合并两个有序链表,合并后依然有序 

Node* Merge(Node* List1, Node* List2)//8合并两个有序单链表
{
if (List1 == NULL) //如果List1为空,则返回List2
{
return List2;
}
else if(List2 == NULL) //如果List2为空,则返回List1
{
return List1;
}
else
{
//找出新链表的头结点 此时List1和List2都不为空,比较两个链表第一个节点的值,新链表指向值较小的那个链表
Node* head = NULL;
if (List1->data > List2->data)
{
head = List2;
List2 = List2->next; //此时,新链表指向List2
}
else
{
head = List1;
List1 = List1->next; //此时,新链表指向List1

}

//尾插
Node* tail = head;
while (List1&&List2) //List1和List2都不为空
{
if (List1->data <List2->data) 选择List1和List2中指向节点中值较小的那个节点,链接到新链表中
{
tail->next= List1;
List1 = List1->next;
}
else
{
tail->next= List2;
List2 = List2->next;
}
tail = tail->next;
}
//while循环出来,可能是List1为空或者List2为空。把不为空的链表直接链接到新表的尾
if (List1)
{
tail->next = List1;
}
else
{
tail->next = List2;
}
return head;
}
}

9.查找单链表的中间节点,要求只能遍历一次链表

这里我们使用快慢指针。快指针每次比慢指针多走一步。当fast走到最后时,slow当好指向是中间节点


当节点数为偶数个,slow指向中间后面的那个。

当节点数为奇数个,slow刚好指向最中间。

Node* FindMiddle(Node* pList)//9遍历一遍找中间数
{
if (pList == NULL)
{
return NULL;
}
Node* slow = pList;
Node* fast = pList;
while (fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}10.查找单链表的倒数第k个节点,要求只能遍历一次链表

求倒数第K个,同样使用快慢指针。不过让快指针先走K-1.步,此时再让快慢指针同时走。当快指针走到最后一个节点,慢指针刚好走到倒数第K个

Node* CountBackward(Node* pList, int k)//10找倒数第K个节点
{
Node* fast = pList;
Node* slow = pList;
while (--k) //让快指针先走K-1
{
fast = fast->next;
}
while (fast&&fast->next)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: