单链表:
链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点有指针域和值域两部分组成,值域就是存储数据的存储单元,指针域就是连接每个结点的地址数据。一般形象的将值域称为data,指针域称为next。链表的家族很大,有单向链表、双向链表、循环链表以及三叉链表等。作为数据结构中举足轻重的代表之一,链表这种数据结构的习题自然而然成为各大公司面试笔试中的重点考察部分,在有限简短的时间里,对于复杂偏难的数据结构一般不会在笔试中出现,而面试中也仅仅可能会在二面中被提及,想要在短时间内搞定一个复杂多变的数据结构实属不易,所以在笔试和一面二面中,像链表、二叉树这些较为简单的知识点既能够考察面试者的逻辑思维能力和优秀的动手能力,又能在基础知识的认知环节让面试官对于面试者的表现有一个初步的印象。
在此从最初的单链表的建立开始,逐步细化,逐步加深,题型之多,不可胜数;精挑细选,难以概全;如有过错,望君指正;此文之用,并无它意;交流学习,与君共勉。
1.单链表的建立:
#include<iostream> using namespace std; #include<assert.h> #include <malloc.h> typedef int DataType; typedef struct SListNode//链表节点结构 { DataType data; struct SListNode* next; }SListNode; void PushBack(SListNode* & pHead, DataType x);//尾插 void PopBack(SListNode* & pHead);//尾删 void PushFront(SListNode* & pHead, DataType x);//头插 void PopFront(SListNode* & pHead);//头删 SListNode* Find(SListNode* pHead, DataType x);//查找元素 void Insert(SListNode* pos, DataType x);//指定位置插入元素 void Erase(SListNode* pos);//指定位置删除元素 void DestoryList(SListNode*& pHead);//删除操作 void DestoryList(SListNode*& pHead) { SListNode* cur = pHead; while (cur) { SListNode* tmp = cur; cur = cur->next; free(tmp); } pHead = NULL; } SListNode* _CreateNode(DataType x)//创建节点 { SListNode* tmp = (SListNode*)malloc(sizeof(SListNode)); tmp->data = x; tmp->next = NULL; return tmp; } void PrintSlist(SListNode* pHead)//打印 { SListNode* cur = pHead; while (cur) { printf("%d->", cur->data); cur = cur->next; } printf("NULL\n"); } void PushBack(SListNode** pHead, DataType x) { assert(ppHead); // 1.链表为空,本身没有节点 // 2.链表不为空,即本身存在一个或多个节点 if(*ppHead == NULL) { *ppHead = _CreateNode(x); } else { // 找尾节点 SListNode* tail = *ppHead; while(tail->next != NULL) { tail = tail->next; } tail->next = _CreateNode(x); } } void PopBack(SListNode*& pHead) { // 1.链表为空 // 2.存在一个节点 // 3.存在多个节点 if (pHead == NULL) { return; } else if (pHead->next == NULL) { free(pHead); pHead = NULL; } else { SListNode* tail = pHead; SListNode* prev = NULL; while (tail->next) { prev = tail; tail = tail->next; } free(tail); prev->next = NULL; } } void PushFront(SListNode* & pHead, DataType x) { // 1.链表空 // 2.链表存在节点 if (pHead == NULL) { pHead = _CreateNode(x); } else { SListNode* tmp = _CreateNode(x); tmp->next = pHead; pHead = tmp; } } void PopFront(SListNode*& pHead) { // 1.链表为空 // 2.一个节点 // 3.多个节点 if (pHead == NULL) { return; } else if (pHead->next == NULL) { free(pHead); pHead = NULL; } else { SListNode* tmp = pHead; pHead = pHead->next; free(tmp); } } SListNode* Find(SListNode* pHead, DataType x) { SListNode* cur = pHead; while (cur) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; } void Insert(SListNode* pos, DataType x) { assert(pos); SListNode* tmp = _CreateNode(x); tmp->next = pos->next; pos->next = tmp; } void Erase(SListNode*& pHead, SListNode* pos) { assert(pos); assert(pHead); if (pHead == pos) { pHead = pHead->next; free(pos); return; } SListNode* prev = pHead; while (prev) { if (prev->next == pos) { prev->next = pos->next; free(pos); break; } prev = prev->next; } }
★至此,单链表的基础操作已经完毕。
2.查找单链表的倒数第k个节点,只遍历一次:
SListNode* FindKthToTail(SListNode* pHead, DataType k) { assert(pHead); if (k == 0) return NULL; SListNode* fast = pHead; SListNode* slow = pHead; while (k-- && fast != NULL) { fast = fast->next; } if (k > 0) { return NULL; } while (fast) { fast = fast->next; slow = slow->next; } return slow; }★注:该题的思想归根结底是一个“快慢指针”的问题,关于”快慢指针“类的题型还有很多,例如:查找单链表的中间节点,判断链表有无环等都是基于这类的思想。
3.查找单链表的中间节点,只遍历一次:
SListNode* FindMiddle(SListNode* pHead) { assert(pHead); SListNode* slow = pHead; SListNode* fast = pHead; while (fast) { fast = fast->next; if (fast != NULL) { slow = slow->next; fast = fast->next; } else { return slow; } } return slow; }
4.在无头单链表的一个非头节点前插入一个节点:
void InsertFront(SListNode*& pHead, SListNode* pos, DataType x) { assert(pHead); SListNode* pHeadName = pHead; while (pHeadName != pos) { pHeadName = pHeadName->next; } if (pHeadName == NULL) { return; } SListNode* cur = _CreateNode(x); cur->next = pos->next; pos->next = cur; cur->data = x; DataType tmp = cur->data; cur->data = pos->data; pos->data = tmp; }
5.删除一个无头单链表的非尾节点:
void DelTail(SListNode*& pHead, SListNode* pos) { assert(pHead); SListNode* del = pos; SListNode* cur = del->next; SListNode* pHeadName = pHead; while (pHeadName != del) { pHeadName = pHeadName->next; } if (pHeadName == NULL) { return; } del->data = cur->data; del->next = cur->next; free(cur); }
6.从尾到头打印单链表:
void TailToHeadPrint(SListNode* pHead) { if (pHead == NULL) { return; } else { TailToHeadPrint(pHead->next); printf("%d->", pHead->data); } }★注:以上递归的方式虽然使得代码看起来很简洁,但当链表的长度很长时,函数递归调用的层级过深时,会造成栈溢出的现象。
★注:以下借助栈“后进先出”的特性,对链表元素实现了从尾到头的输出。
void TailToHeadPrint(SListNode* pHead) { std::stack<SListNode*>nodes; SListNode* pNode = pHead; while (pNode != NULL) { nodes.push(pNode); pNode = pNode->next; } while (!nodes.empty()) { pNode = nodes.top(); printf("%d\t", pNode->data); nodes.pop(); } }
7.约瑟夫环的实现:
SListNode* Joseph(SListNode*& pos, int K) { assert(pos); if (K > 0) { SListNode *tmp = pos; while (1) { int count = K; while (--count) { <span style="white-space:pre"> </span>tmp = tmp->next; } if (tmp->next == tmp) return tmp; SListNode *cur = tmp->next;//删除tmp位置的结点,思路:pos与下一位置结点元素值进行交换后,删除下一结点 DataType Tmpcount = cur->data; tmp->next = cur->next; cur->data = tmp->data; tmp->data = Tmpcount; free(cur); } } return NULL; }
8.判断单链表是否带环?若带环,求环长度,求环入口点:
SListNode* IsRing(SListNode *&pHead) //判断链表是否有环,求交点 { //判断链表是否为空、是否有一个或多个节点 //思路:两个指针从头开始一个快指针(2步)一个慢指针(1步),若最后可以相交,则链表有环 if (pHead) { SListNode *fast = pHead; SListNode *slow = pHead; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; if (slow == fast) return fast; } return NULL; } return NULL; } int RingLength(SListNode*& pHead)//求链表环长度 { //先判断链表有环 //思路:从IsRing得到的点开始走一圈环,记录结点的个数 if (pHead)//判空 { SListNode* point = IsRing(pHead); if (point != NULL)//无环情况 { int count = 1; SListNode *tmp = point->next; while (tmp != point) { tmp = tmp->next; count++; } return count; } } return 0; } SListNode* RingEntry(SListNode*& pHead)//找环入口 { //判是否为空、有环 //思路:判断有环,找到快慢指针的交点,然后一个指针从交点开始,一个从头开始,每次一步,相遇点就为入口节点 if (pHead)//判空 { SListNode *tmp = IsRing(pHead); if (tmp)//判有环 { SListNode *cur = pHead; while (cur != tmp) { cur = cur->next; tmp = tmp->next; } return cur; } return NULL; } return NULL; } int _LengthNode(SListNode*& pHead)//求链表长度 { if (pHead) { SListNode *tmp = pHead; int count = 0; while (tmp) { count++; tmp = tmp->next; } return count; } return 0; }