1.比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?
顺序表:内存中地址连续,优点是随机访问比较便捷快速,创建也比较简单,随机查找比较方便,可以直接给出下标,排序也方便 简单。
缺点: 不够灵活,删除增加的工作量叫大,比较麻烦,长度不能实时变化
适用场景:适用于需要大量访问元素的 而少量增添/删除元素的程序
单链表:内存中地址不是连续的,优点是插入删除比较方便,长度可以实时变化。
缺点: 不支持随机查找,查找元素需要遍历。
适用场景 : 适用于需要进行大量增添/删除元素操作 而对访问元素无要求的程序
2.从尾到头打印单链表
void PrintTailToHead(Node* pHead) //递归实现从尾到头打印单链表
{
if (pHead == NULL)
{
return;
}
PrintTailToHead(pHead->next); //递归时,下一次的pHead其实是上一个pHead的next
printf("%d ", pHead->data);
}
3.删
除一个无头单链表的非尾节点
void EraseNonTail(Node* pos) //删除一个无头链表的非尾节点 { Node* next; assert(pos&&pos->next); //pos不能为空也不能为尾 next = pos->next; pos->data = next->data; //先让后面的值覆盖掉前面的值 pos->next = next->next; //再让前面的节点指向后面节点的后面 即1 2 3 4 5变为1 2 4 4 5,再变为1 2 4 5 free(next); //把下一个节点释放掉 }
4.在无头单链表的一个节点前插入一个节点
思路一:
先链上,再交换其值。但此方法需要额外编写一个Swap函数,略微复杂。
思路二:
先交换其值,再链接。
思路二的代码如下:
void InsertFront(Node* pos, DataType x) //在无头单链表的一个节点前插入一个节点
{
Node* next, *tmp;
assert(pos);
next = pos->next; //记录插入点之后的节点
tmp = BuyNode(pos->data); //增容一个节点,其值为插入位置节点的值
pos->data = x; //将要插入节点值赋给插入位置节点
pos->next = tmp; //将其链起来
tmp->next = next;
}
5.单链表实现约瑟夫环
Node* Josephus(Node* hus, size_t k) //单链表实现约瑟夫环
{
Node* man, *next;
assert(hus); //环不为空
man = hus; //记录环的头
while (man->next != man) //当最后只剩一个节点时结束
{
int count = k; //记录步数
while (--count) //走k-1步
{
man = man->next;
}
next = man->next; //为了删除第k个节点,记录其下一个节点
man->data = next->data; //将下一节点的值赋给第k个节点
man->next = next->next; //使值已经改变的第k个节点指向下一节点的下一节点
free(next); //释放下一节点。即原1 2 3 4 5走3步时,变为1 2 4 4 5,再变为1 2 4 5
}
return man; //返回最后只剩一个节点的man(幸存者)
}
6.逆置/反转单链表(重中之重)
思路一:
将指针逆置,本来1->2->3->4,改为1<-2<-3<-4。
void ReverseList(Node** ppNode) //逆置反转 { Node* n0, *n1, *n2 ; if (*ppNode == NULL) { return; } n0 = NULL; n1 = *ppNode; n2 = n1->next; while (n1) //n1为空时结束 { //逆置 n1->next = n0; //后移 n0 = n1; n1 = n2; if (n2 != NULL) //if(n1==NULL) { break; } { n2 = n2->next; } } *ppNode = n0; }
思路二:
利用逐个头插到另一个空节点的方法。
void ReverseList(Node** ppNode) //逆置反转 { Node* newHead = NULL; Node* cur = *ppNode; while (cur != NULL) { Node* next = cur->next; //记录下一个节点 cur->next = newHead; //头插 newHead = cur; //cur变为头结点 cur = next; } *ppNode = newHead; }
7.单链表排序(冒泡排序&快速排序)
冒泡排序:
void SortList(Node* pHead) //冒泡排序(不需要头,所以不传二级指针) { //此处不能用断言,只有一定不为空时才能用断言 if (pHead == NULL || pHead->next == NULL) { return; } Node* tail = NULL; while (tail != pHead) { int exchange = 0; //标记变量,如果链表本身已经有序,一趟比较之后不用再做比较 Node* cur = pHead, *next = cur->next; //为什么这条语句不可以放在全部while外定义? while (next != tail) { if (cur->data > next->data) { exchange = 1; DataType t = cur->data; cur->data = next->data; next->data = t; } cur = cur->next; next = next->next; } if (exchange == 0) { break; } tail = cur; } }
8.合并两个有序链表,合并后依然有序 (重中之重)
Node* MergeList(Node* list1, Node* list2) //两个有序链表合并后仍为有序链表 { Node* list,*tail; if (list1 == NULL) { return list2; } if (list2 == NULL) { return list1; } //两者都为空时反正返回为空 if (list1->data < list2->data) //先确定list { 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; //因为此时新链表已经链上了元素,tail后有元素 } if (list1) //如果全部比较后,list1还有元素,直接链到tail后 { tail->next = list1; } if (list2) //如果全部比较后,list2还有元素,直接链到tail后 { tail->next = list2; } return list; //list不能动,因为它标识了新链表的头 }
9.查找单链表的中间节点,要求只能遍历一次链表
利用快慢指针。
Node* FindMidNode(Node* pHead) //找单链表的中间节点 { Node* slow = pHead, *fast = pHead; while (fast && fast->next && fast->next->next) //预防fast为空,奇数时预防fast的next为空,偶数时保证输出前一个元素 { slow = slow->next; //slow每次走一步,fast每次走两步,当fast到尾时,slow在中间 fast = fast->next->next; } return slow; }
10.查找单链表的倒数第k个节点,要求只能遍历一次链表
利用快慢指针。先考虑不足K个节点时,再考虑相差k步和k-1步的差异,差k步在fast为空时结束,差k-1步在fast为尾时结束。
Node* FindKNode(Node* pHead,size_t k) { Node* slow = pHead, *fast = pHead; while (--k) // --k走k-1步,k--走k步(上去就减去了1) { if (fast == NULL); { return NULL; } fast = fast->next; } while (fast->next) //快慢指针差k-1步时,fast到尾即可结束 { slow = slow->next; fast = fast->next; } return slow; }