链表c实现——附相关面试题
写链表全部需要注意的点
- typedef 把链表结点指针类型重命名,pNode是结构体指针类型
- 在每一个方法里首先要处理非法数据,传入指针为空就可能造成崩溃
- 删除结点相关的方法里,要提前保存要删除的结点,防止内存泄漏,防止野指针
- 如果牵扯到可能改变头结点指向,就得传入二级指针pNode*,或者返回值为pNode
- 写方法时首先考虑空链表,其次是一般情况,还要考虑只有头结点的
- 如果有些时候最后一次判断条件很别扭,比如反转链表三指针,尝试改变语句顺序
- 小技巧,找最后一个结点条件用p->next != NULL, 而其他一般是p !=NULL
- 灵活!一个指针不够就加指针,ppre上一个,pnxt下一个,pdel要删除的
- 一些面试题会用到两个指针,一快一慢,组合起来能完成很多任务
github地址
https://github.com/zzaiyuyu/linkList/tree/master/linkList
linkList.h
#pragma once
typedef int DataType;
//重命名pNode为指向结构体指针的类型
typedef struct ListNode {
struct ListNode * pNext;
DataType data;
}Node, *pNode;
//初始化
void SListInit(pNode* pHead);
//尾插
void linkListPushBack(pNode * pHead, int data);
//尾删
void linkListPopBack(pNode * pHead);
//头插
void linkListPushFront(pNode * pHead, int data);
//头删
void linkListPopFront(pNode * pHead);
//任意位置插入
void linkListInsert(pNode *pHead, int data, pNode pos);
//任意位置删除
void linkListErase(pNode *pHead, pNode pos);
//寻找值为data的结点
pNode findNode(pNode pHead, int data);
//返回链表长度
int SListSize(pNode pHead);
// 判断链表是否为空
int SListEmpty(pNode pHead);
// 销毁链表
void SListDestroy(pNode* pHead);
/******************************面试题**************************************/
void reverse_print(pNode pHead);//从尾到头打印链表
void erase_node(pNode *pHead, pNode pos);//O(1)删除结点
void insert_node(pNode *pHead, int data, pNode pos);//O(1)插入结点
int joseph_circle(pNode *pHead, int num);//单链表约瑟夫环,返回最后一个剩下的结点值
void reverse_llist(pNode *pHead);//反转链表——三指针
pNode ReverseSListOP(pNode pHead); //反转链表--头插法
void bubble_llist(pNode pHead); // 冒泡排序链表
pNode merge_llist(pNode pa, pNode pb);//合并有序链表
pNode FindMiddleNode(pNode pHead);// 查找链表的中间结点,要求只能遍历一次链表
pNode FindLastKNode(pNode pHead, int K);// 查找链表的倒数第K个结点,要求只能遍历一次链表
pNode DeleteLastKNode(pNode pHead, int K);// 删除链表的倒数第K个结点,要求只能遍历一次链表
int IsSListCross(pNode pHead1, pNode pHead2);// 判断两个单链表是否相交---链表不带环
pNode GetCorssNode(pNode pHead1, pNode pHead2);// 求两个单链表相交的交点---链表不带环
linkList.c 链表基本操作
#include "linkList.h"
#include <stdio.h>
#include <assert.h>
#include <malloc.h>
//错误的创建新节点方式
//Node buyNewNode(int data)
//{
// Node newNode;
// newNode.data = data;
// newNode.pNext = NULL;
// return newNode;
//}
void SListInit(pNode* pHead)
{
assert(pHead);
*pHead = NULL;
}
//创建新节点
pNode buyNode(int data)
{
pNode pNew = NULL;
pNew = (pNode)malloc(sizeof(Node));
if (pNew == NULL) {
printf("分配结点失败");
return NULL;
}
pNew->data = data;
pNew->pNext = NULL;
return pNew;
}
void linkListPushBack(pNode * pHead, int data)
{
//保证链表存在
assert(pHead);
pNode pCur = NULL;
//两种情况,空链表和非空
if (NULL == *pHead) {
*pHead = buyNode(data);
}
else {
pCur = *pHead;
while (NULL != (pCur)->pNext ) {
pCur = pCur->pNext;
}
pCur->pNext = buyNode(data);
}
}
void linkListPopBack(pNode * pHead)
{
assert(pHead);
if (NULL == *pHead) {
return;
}
pNode pPre = NULL;
pNode pDel = NULL;
pDel = *pHead;
//只有头节点和有1个以上的结点
if (NULL == pDel->pNext) {
free(*pHead);
*pHead = NULL;
}
else {
while (NULL != pDel->pNext) {
pPre = pDel;
pDel = pDel->pNext;
}
free(pDel);
pPre->pNext = NULL;
}
}
void linkListPushFront(pNode * pHead, int data)
{
assert(pHead);
if (NULL == *pHead) {
*pHead = buyNode(data);
}
else {
pNode pNew = buyNode(data);
pNew->pNext = *pHead;
(*pHead)= pNew;
}
}
void linkListPopFront(pNode * pHead)
{
assert(pHead);
if(NULL == *pHead){
return;
}
else {
pNode pDel = *pHead;
(*pHead) = (*pHead)->pNext;
free(pDel);
}
}
void linkListInsert(pNode *pHead, int data, pNode pos)
{
assert(pHead);
if (NULL == *pHead || NULL == pos) {
return;
}
//两种情况,头插或者任意位置插入
if ((*pHead == pos)) {
pNode pNew = buyNode(data);
pNew->pNext = *pHead;
*pHead = pNew;
}
else {
pNode pCur = *pHead;
pNode pPre = NULL;
while (pCur && pCur != pos) {
pPre = pCur;
pCur = pCur->pNext;
}
//找到和没找到pos两种情况
if (pCur) {
pNode pNew = buyNode(data);
pNew->pNext = pCur;
pPre->pNext = pNew;
}
}
}
void linkListErase(pNode *pHead,pNode pos)
{
assert(pHead);
if (NULL == *pHead || NULL == pos) {
return;
}
if (*pHead == pos) {
pNode pDel = *pHead;
*pHead = (*pHead)->pNext;
free(pDel);
}
else {
pNode pPre = NULL;
pNode pCur = *pHead;
while (pCur && pCur != pos) {
pPre = pCur;
pCur = pCur->pNext;
}
if(pCur){
pPre->pNext = pCur->pNext;
free(pCur);
}
}
}
pNode findNode(pNode pHead, int data)
{
if (NULL == pHead) {
return NULL;
}
while (pHead) {
if (pHead->data == data) {
return pHead;
}
pHead = pHead->pNext;
}
return NULL;
}
int SListSize(pNode pHead)
{
if (NULL == pHead) {
return 0;
}
int count = 0;
while (pHead) {
pHead = pHead->pNext;
count++;
}
return count;
}
int SListEmpty(pNode pHead)
{
return pHead ? 1 : 0;
}
void SListDestroy(pNode* pHead)
{
assert(pHead);
if (NULL == *pHead) {
return;
}
pNode pDel = NULL;
//因为最后*pHead一定是NULL,不存在野指针
while (*pHead) {
pDel = *pHead;
*pHead = (*pHead)->pNext;
free(pDel);
}
}
interview.c 链表相关面试题
#include "linkList.h"
#include <stdio.h>
#include <assert.h>
//从尾到头打印链表
void reverse_print(pNode pHead)
{
if (NULL == pHead) {
return;
}
reverse_print(pHead->pNext);
printf("%d", pHead->data);
}
//O(1)删除非尾结点
void erase_node(pNode *pHead, pNode pos)
{
assert(pHead);
if (NULL == *pHead || NULL == pos) {
return;
}
//分为头删和删除非尾结点两种情况
if (pos == *pHead) {
pNode pDel = *pHead;
*pHead = (*pHead)->pNext;
free(pDel);
}
else {
pNode pDel = pos;
pDel = pDel->pNext;
if (pDel->pNext) {
pos->data = pDel->data;
pos->pNext = pDel->pNext;
free(pDel);
}
}
}
//O(1)插入结点
void insert_node(pNode *pHead, int data, pNode pos)
{
assert(pHead);
if (NULL == *pHead || NULL == pos) {
return;
}
pNode pNew = NULL;
pNew = (pNode)malloc(sizeof(Node));
if (NULL == pNew) {
return;
}
pNew->data = pos->data;
pNew->pNext = pos->pNext;
pos->data = data;
pos->pNext = pNew;
}
//单链表约瑟夫环,返回最后一个剩下的结点值
int joseph_circle(pNode *pHead, int num)
{
assert(pHead);
if (NULL == *pHead || num <= 1) {
return -1;
}
pNode pStr = *pHead;
pNode pCur = NULL;
pNode pDel = NULL; //需要被剔除的结点
while (pStr->pNext != pStr) {
//报数
pCur = pStr;
int count = num;
while (--count) {
pCur = pCur->pNext;
}
//剔除
pDel = pCur;
pCur = pCur->pNext;
pStr->pNext = pCur;
pStr = pCur;
free(pDel);
}
return pStr->data;
}
//反转链表——三指针
void reverse_llist(pNode *pHead) {
assert(pHead);
if (NULL == *pHead) {
return;
}
pNode pPre = NULL;
pNode pCur = *pHead;
pNode pNxt = pCur;
while (pCur) { //这个题判断比较精妙,如果以pNxt为条件不妥
pNxt = pCur->pNext; //这句放在pcur改变之前!
pCur->pNext = pPre; //当觉得最后一次判断条件比较复杂时,尝试调整语句顺序和判断条件
pPre = pCur;
pCur = pNxt;
}
*pHead = pPre;
}
//反转链表--头插法
pNode ReverseSListOP(pNode pHead)
{
if (NULL == pHead) {
return NULL;
}
pNode pNew = NULL;
pNode pNxt = NULL;
//处理第一个结点
pNew = pHead;
pHead = pHead->pNext;
pNew->pNext = NULL; //这两步顺序很关键,新的第一个头结点要指空,
//因为是头插,到后面不会再改动这个
while (pHead) {
pNxt = pHead->pNext;
pHead->pNext = pNew;
pNew = pHead;
pHead = pNxt;
}
return pNew;
}
//冒泡排序链表
void bubble_llist(pNode pHead) {
if (NULL == pHead) {
return;
}
//找到尾结点
pNode pTail = pHead;
while (pTail->pNext) {
pTail = pTail->pNext;
}
pNode pCur = NULL;
pNode pPre = NULL;
//当ptail指向头说明只有一个节点了,不需要比较
while (pTail != pHead) {
pCur = pHead;
//每趟比到pcur在ptail前就可以了,因为pcur与pcur->next值比较
while (pCur != pTail) {
if ((pCur->data) > (pCur->pNext->data)) {
int tmp;
tmp = pCur->data;
pCur->data = pCur->pNext->data;
pCur->pNext->data = tmp;
}
pPre = pCur;
pCur = pCur->pNext;
}
pTail = pPre; //ptail每次后退一个位置
}
}
//合并两个有序链表
pNode merge_llist(pNode pa, pNode pb)
{
pNode pNew = NULL;
//处理头结点
if (pa && (pa->data <= pb->data)) {
pNew = pa;
pa = pa->pNext;
}
else if(pb){
pNew = pb;
pb = pb->pNext;
}
pNode pNewHead = pNew;
while (pa && pb) {
if (pa->data <= pb->data) {
pNew->pNext = pa;
pa = pa->pNext;
}
else {
pNew->pNext = pb;
pb = pb->pNext;
}
pNew = pNew->pNext;
}
//走到这里,pa或者pb为空
if (pa) {
pNew->pNext = pa;
}
else {
pNew->pNext = pb;
}
return pNewHead;
}
//查找链表的中间结点
pNode FindMiddleNode(pNode pHead)
{
if (NULL == pHead) {
return NULL;
}
//考虑两种情况,奇数个和偶数个结点
pNode pLow = pHead;
pNode pFst = pHead;
pNode pPre = NULL; //如果偶数个结点,返回较小的
while (pFst && pFst->pNext) {
pPre = pLow;
pLow = pLow->pNext;
pFst = pFst->pNext->pNext;
}
//走到这里pfst如果是空就是偶数个结点
if (NULL == pFst) {
return pPre;
}
else {
return pLow;
}
}
//倒数第K个结点
pNode FindLastKNode(pNode pHead, int K)
{
if (NULL == pHead || K <= 0) {
return NULL;
}
pNode pFst = pHead;
while (K--) {
if (NULL == pFst) { //这个条件一定在走fst之前
return NULL; //一是保证即使k很大也能退出,二保证pFst=NULL的情况正常出去
}
pFst = pFst->pNext;
}
pNode pLow = pHead;
while (pFst) {
pFst = pFst->pNext;
pLow = pLow->pNext;
}
return pLow;
}
// 删除链表的倒数第K个结点
pNode DeleteLastKNode(pNode pHead, int K)
{
if (NULL == pHead || K <= 0) {
return NULL;
}
pNode pFst = pHead;
while (K--) {
if (NULL == pFst) { //这个条件一定在走fst之前
return NULL; //一是保证即使k很大也能退出,二保证pFst=NULL的情况正常出去
}
pFst = pFst->pNext;
}
pNode pLow = pHead;
pNode pPre = NULL;
while (pFst) {
pPre = pLow;
pFst = pFst->pNext;
pLow = pLow->pNext;
}
//这里保证找到了倒数第K个结点pLow,考虑是不是头结点
if (pHead == pLow) {
pNode pNew = pHead->pNext;
free(pHead);
return pNew;
}
else {
pPre->pNext = pLow->pNext;
free(pLow); //如果在别的程序使用plow存在内存泄漏?
return pHead;
}
}
// 判断两个单链表是否相交---链表不带环
int IsSListCross(pNode pHead1, pNode pHead2)
{
if (NULL == pHead1 || NULL == pHead2) {
return -1;
}
//找两个链表的最后一个结点
while (pHead1->pNext) {
pHead1 = pHead1->pNext;
}
while (pHead2->pNext) {
pHead2 = pHead2->pNext;
}
if (pHead1 == pHead2) {
return 1;
}
else {
return -1;
}
}
//求两个单链表相交的交点--- 链表不带环
pNode GetCorssNode(pNode pHead1, pNode pHead2)
{
if (NULL == pHead1 || NULL == pHead2) {
return NULL;
}
int len1 = 0;
int len2 = 0;
pNode pCur1 = pHead1;
pNode pCur2 = pHead2;
while (pCur1) {
len1++;
pCur1 = pCur1->pNext;
}
while (pCur2) {
len2++;
pCur2 = pCur2->pNext;
}
int diff = len1 - len2;
while (0 != diff) {
if (diff > 0) {
pHead1 = pHead1->pNext;
diff--;
}
else {
pHead2 = pHead2->pNext;
diff++;
}
}
//走到这里两个指针距相交点有相同距离
while (pHead1 != pHead2) {
pHead1 = pHead1->pNext;
pHead2 = pHead2->pNext;
}
return pHead1;
}
test.c
#include "linkList.h"
#include <stdio.h>
void test_interview(pNode *pHead);
int main()
{
//所有测试函数传入的头指针
pNode pHead = NULL;
//linkListPushBack(&pHead, 5);
//linkListPushBack(&pHead, 4);
//linkListPushBack(&pHead, 3);
//linkListPopBack(&pHead);
//linkListPopBack(&pHead);
//linkListPopBack(&pHead);
//linkListPushFront(&pHead, 10);
//linkListPushFront(&pHead, 9);
//pNode tmp10 = findNode(pHead, 10);
//pNode tmp9 = findNode(pHead, 9);
//linkListInsert(&pHead, 7, tmp9);
//linkListInsert(&pHead, 7, tmp10);
//linkListErase(&pHead, findNode(pHead, 7));
//linkListErase(&pHead, findNode(pHead, 7));
//linkListPopFront(&pHead);
//linkListPopFront(&pHead);
//int ret = SListEmpty(pHead);
//int tmp = SListSize(pHead);
//SListDestroy(&pHead);
test_interview(&pHead);
}
//测试面试题函数
void test_interview(pNode *pHead)
{
//在这个函数里,pHead是指向头指针的指针,*pHead是头指针
linkListPushBack(pHead, 2);
linkListPushBack(pHead, 4);
linkListPushBack(pHead, 7);
linkListPushBack(pHead, 3);
linkListPushBack(pHead, 5);
//链表倒序打印
//pNode pNow = *pHead;
//reverse_print(*pNow);
//O(1)删除结点
//erase_node(pHead, findNode(*pHead, 3));
//O(1)插入结点
//insert_node(pHead, 88, findNode(*pHead, 5));
//约瑟夫
//pNode pTail = findNode(pHead, 1);
//pTail->pNext = *pHead;
//int ret = joseph_circle(pHead, 1);
//反转——三指针
//reverse_llist(pHead);
//反转——头插
//pNode pNew = ReverseSListOP(*pHead);
//冒泡
//bubble_llist(pHead);
//合并有序
pNode pb = NULL;
linkListPushBack(&pb, 1);
linkListPushBack(&pb, 3);
linkListPushBack(&pb, 5);
pNode pNew = merge_llist(*pHead, pb);
//查找中间结点
//pNode pMid = FindMiddleNode(*pHead);
//倒数第k个
//pNode pLastK = FindLastKNode(*pHead, 6);
//删除倒数第k个
//pNode pNew = DeleteLastKNode(*pHead, 2);
//判断是否相交
//pNode pTail = findNode(pb, 5);
//pTail->pNext = findNode(*pHead, 7);
//int ret = IsSListCross(*pHead, pb);
//pNode ret = GetCorssNode(*pHead, pb);
}