1.比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?
链表 | 顺序表 | |
---|---|---|
每个元素的访问 | 每个节点都需要通过指针找到内存然后加载到缓存中 | 内存访问时,不需要多次从内存到缓存的步骤 |
头插,头删,中间位置 | 直接增加或删除节点 | 在插入或删除之前要先对之后的数据进行挪动 |
尾插,尾删 | 需要对空间进行操作 | 直接对数据进行操作,空间是之前一次性开辟好的 |
空间操作 | 每次对一个节点大小进行操作,不会造成空间浪费 | 通常一次性开辟好空间,扩容时直接增加两空间,会造成空间浪费 |
2.从尾到头打印单链表
- 递归法,最后一个节点最先打印
void PrintListFromTailToHead_R(ListNode* head)
{
if (head)
{
if (head->next)
PrintListFromTailToHead_R(head->next);
cout << head->val << " ";
}
}
3.删除一个无头单链表的非尾节点
因为不知道链表的头节点,所以不能直接常规删除(找到要删除的节点位置直接删除),将所要删除的节点A之后一个节点B数据与A数据交换,然后删除交换后的B节点。如图,图中,要删除数为3的节点,先将存有数据3、4的节点中的数据进行交换,然后删除交换后的节点(绿色框中的存有数据3的节点):
void EraseNonTail(ListNode* pos)
{
Node* next;
assert(pos && pos->next)
next = pos->next;
pos->data = next->data;
pos->next = next->next;
free(next);
}
4.在无头单链表的一个节点A前插入一个节点 B
和问题3中的一样,不知道头节点,不能用常规头插,将问题中的头插变为尾插,插入之后与节点A的数据进行交换,就基本完成了无头单链表头插的要求
void InsertNonHead(ListNode* pos, DataType x)
{
assert(pos);
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
node->next = pos->next;
node = pos->next;
node->data = pos->data;
pos->data = x;
}
5.单链表实现约瑟夫环
约瑟夫环:约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
解法:用一个计数器实现约瑟夫计数机制,将链表的尾与头衔接,依次删除节点,所剩最后一个节点时,就是要求的节点
ListNode* Josephus(ListNode* hus, int k)
{
ListNode *man, *next;
assert(hus);
man = hus;
while(man->next != man)
{
int count = k;
while(--count)
{
man = man->next;
}
next = man->next;
man->data = next->data;
man->next = next->next;
free(next);
}
return man;
}
6.逆置/反转单链表
给一个新的链表的头(Newlist == NULL):用两个指针,一个指向目标链表的头(mov),另一个用来保存 mov 后面一个节点的地址(sto)。在保存 sto 之后,将 mov 指向的节点摘出来头插在新链表头前面(Newlist),Newlist 指向当前头插后的新链表的头,依次循环上述步骤头插,直到目标链表 list 被遍历完。
方法一:
void ReverseList(ListNode** ppNode)
{
ListNode* n0, *n1, *n2;
if(*ppNode == NULL)
{
return;
}
n0 = NULL;
n1 = *ppNode;
n2 = (*ppNode)->next;
while(n1)
{
n1->next = n0;
n0 = n1;
n1 = n2;
if(n2)
{
n2 = n2->next;
}
}
*ppNode = n0;
}
方法二:
void ReverseList1(ListNode** ppNode)
{
ListNode* cur, *newHead;
newHead = NULL;
cur = *ppNode;
while(cur)
{
//记录下一个
ListNode* next = cur->next;
//头插
cur->next = newHead;
newHead = cur;
cur = next;
}
*ppNode = newHead;
}
7.单链表排序(冒泡排序&快速排序)
冒泡排序或快速排序,交换的是节点中的数据。
void ListSort(ListNode* pNode)
{
if((pNode == NULL) || (pNode->next ==NULL))
{
return;
}
ListNode* tail = NULL;
int exchange = 0;
while(pNode != tail )
{
ListNode* cur = pNode;
ListNode* next = cur->next;
while(next != tail)
{
if((cur->data) > (next->data))
{
exchange = 1;
DataType tmp = NULL;
tmp = cur->data;
cur->data = next->data;
next->data = tmp;
}
cur = cur->next;
next = next->next;
}
tail = cur;
if(exchange = 0)
{
break;
}
}
}
8.合并两个有序链表,合并后依然有序
创建新链表(Newlist),比较两个目标链表的头节点中的数据,摘出其中较小者(若相等规定取其一)对 Newlist 尾插,依次遍历两个目标链表,直到其中一个遍历完成,将另一个所剩节点衔接在新链表之后。
ListNode* MergeList(ListNode* List1, ListNode* List2)
{
ListNode* List, *tail;
if(List1 == NULL)
{
return List2;
}
if(List2 == NULL)
{
return List1;
}
if(List1->data < List2->data)
{
List = List1;
List1 = List1->next;
}
else
{
List = List2;
List2 = List2->next;
}
tail = List;
while(List1 && List2)
{
if(List1->data < List2->data)
{
tail->next = List1;
List1 = List1->next;
}
else
{
tail->next = List2;
List2 = List2->next;
}
tail = tail->next;
}
if(List1 == NULL)
{
tail->next = List2;
}
else
{
tail->next = List1;
}
return List;
}
9.查找单链表的中间节点,要求只能遍历一次链表
快慢指针(fast、slow),fast 每次走两个节点,slow 每次走一个节点,在快指针遍历完的时候,慢指针刚好走了一半。此时,慢指针所指节点为中间节点
ListNode* FindMidNode(ListNode* pHead)
{
ListNode* slow = pHead;
ListNode* fast = pHead;
while(fast && fast->next && fast->next->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
10.查找单链表的倒数第k个节点,要求只能遍历一次链表
快慢指针(fast、slow),fast 与 slow 每次都走一个节点,但是 fast 比 slow 先走 k 个节点,这样,快慢指针直接总是隔开 k 个节点,在 fast 遍历完之后,slow 所指节点为单链表倒数第 k 个节点
ListNode* FindTailKNode(ListNode* pHead, size_t k)
{
ListNode* slow = pHead;
ListNode* fast = pHead;
while(--k)
{
if(fast == NULL)
{
return NULL;
}
fast = fast->next;
}
while(fast->next)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}