链表和顺序表的面试题(初级版)

下面的面试题用到的数据类型及函数
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;
typedef struct SLinkList
{
	DataType data;
	struct SLinkList*PNext;
}SNode;
// 初始化 
void SLinkList_InIt(SNode **Phead)
{
	assert(Phead != NULL);
	*Phead = NULL;
}
1.从尾到头打印单链表

有两种方法:

  •  递归法

       注意事项:递归的结束结束条件比较重要,当链表的指针走到尾部(NULL)时结束递归。

下面是原理图,便于理解:


void SLinkList_Print(SNode *Phead)
{
	assert(Phead != NULL);
	while (Phead != NULL)//递归结束条件
	{
		printf("%d ", Phead->data);
		Phead = Phead->PNext;
	}
	printf("\n");
}
  • 循环法

               思路:定义两个指针,一个从头开始遍历Pnode,另外一个从尾部开始Pend每次当Pnode等于Pend的          时候,更新Pend的值,使它往前走一步,Phead又一次从头遍历,直到Pend走到Phead的位置时打印完成。

void ReverseLoop(SNode*Phead)
{
	assert(Phead != NULL);
	SNode*Pnode = Phead;
	SNode*Pend = NULL;
	if (Pnode == NULL)
	{
		printf("NULL\n");
		return;
	}
	while (Pend != Phead)//当Pend的位置从链表的尾部移到头部时说明打印完成
	{
		for (Pnode = Phead; Pnode->PNext != Pend; Pnode = Pnode->PNext)//让Pnode每一次走到Pend的位置停下来让后打印Pnode->data
		{
			;
		}
		printf("%d ", Pnode->data);
		Pend = Pnode;
	}
	printf("\n");
} 

原理图:


2.删除一个无头单链表的分为节点(不能遍历链表)

之前我们找单链表中的某个节点的时候是遍历整个链表,现在要求不能遍历链表,那我们就另辟蹊径。

思路:

  • 将要删除的节点的下一个节点Pnext中的data放到要删除的节点Pnode中
  • 删除Pnext
  • 让要删除的节点Pnode指向PNext的下一个节点
void ListDel(SNode**Pos)
{
	assert(Pos != NULL);
	assert((*Pos)->PNext != NULL);
	SNode*PNode = (*Pos)->PNext;
	(*Pos)->data = PNode->data;
	(*Pos)->PNext = PNode->PNext;
	free(PNode);
} 

原理图:


3.在无头链表的一个节点前插入一个节点(不能遍历链表)

这个问题其实和上面的那个问题十分的相似,

思路:

  • 因为不能遍历链表,所以这里是将要插入的位置对应的节点的data给要插入的新节点的data
  • 然后将要插入的内容(data)放到要插入的位置的节点的内容中即Posnode->data=data
  • 将存有要插入位置data的新节点的PNext改为原来要插入的位置对应节点的下一个节点
  • 使要插入的位置的节点的PNext指向新的节点

原理图:


//创建一个新结点
static SNode* BuySListNode(DataType data)
{
	SNode *pNewNode = (SNode*)malloc(sizeof(SNode));
	if (pNewNode == NULL)
	{
		assert(0);
		return NULL;
	}
	pNewNode->data = data;
	pNewNode->PNext = NULL;
	return pNewNode;
}
//头部插入
void PushFront(SNode**Phead, DataType data)
{
	assert(Phead != NULL);
	if (*Phead == NULL)
	{
		*Phead = BuySListNode(data);
		return;
	}
	else
	{
		SNode *pNewNode = BuySListNode(data);
		pNewNode->PNext = *Phead;
		*Phead = pNewNode;

	}

}
void ListInsert(SNode**Posnode, DataType data)
{
	assert(Posnode != NULL);
	if (*Posnode == NULL)
	{
		PushFront(Posnode, data);
		return;
	}
	SNode*Pnode = *Posnode;
	SNode*PNewnode = BuySListNode(Pnode->data);
	Pnode->data = data;
	PNewnode->PNext = Pnode->PNext;
	Pnode->PNext = PNewnode;
}

4.单链表实现约瑟夫环

思路:

  • 报数
           1. 先将单链表围成一个环
            2.用一个指针遍历链表,如果遇到要删的数字保存它的前一个节点
  • 删除节点,然后继续报数,直至只剩一个节点退出(直接删除和替换删除。替换法删除无法删除最后一个节点,但是这里是一个循环的环所以不考虑)

原理图:


SNode* ListJosephCircle(SNode*PFirst, int k)
{
	assert(PFirst != NULL);
	if (PFirst == NULL)
	{
		return NULL;
	}
	SNode *pDel = PFirst;
	SNode *pCur = NULL;
	int n = 0;
	//将单链表构成环
	while (pDel->PNext != NULL)
	{
		pDel = pDel->PNext;
	}
	pDel->PNext = PFirst;

	while (pDel->PNext != pDel)//当只剩下一个节点的时候停止循环
	{
		n = k;
		while (--n)//这里每次循环k-1次,注意是--n
		{
			pCur = pDel;
			pDel = pDel->PNext;
		}
		pCur->PNext = pDel->PNext;//更新链表删除要删的结点
		free(pDel);
		pDel = pCur->PNext;//更新游标重新开始从k开始遍历链表
	}
	return pDel;
}

5.逆置/反转单链表

这里有两种方法。

  • 改变Pnext的指向

让链表内部每个节点的PNext反向指向前一个(三个指针)  时间复杂度为O(N),利用三者指针遍历链表P1,P2,P3,让他们之间的Pnext反过来指向前一个。 

原理图:


//让链表内部每个节点的PNext反向指向前一个(三个指针)  时间复杂度为O(N)
void ReverseList(SNode**PFirst)
{
	assert(PFirst != NULL);
	SNode*P1 = NULL;
	SNode*P2 = *PFirst;
	SNode*P3 = (*PFirst)->PNext;
	while (P2 != NULL)
	{
		P2->PNext = P1;
		P1 = P2;
		P2 = P3;
		if (P3 != NULL)
		{
			P3 = P3->PNext;
		}
	}
	*PFirst = P1;
}

  • 头删头插

思路:
1.新建一个链表将旧链表从头部开始把节点一个一个POP出来
2.将POP出来的节点头插到新的链表中
注意:

   这里是头删所以当删掉Pnode的第一个节点后不需要使Pnode指向它原来的下一个节点(不然会跳过节点)

//时间复杂度为O(1)
SNode* PopFront(SNode**Phead)
{
	assert(Phead != NULL);
	if (*Phead != NULL)
	{
		SNode* pNewNode = *Phead;
		*Phead = (*Phead)->PNext;
		return pNewNode;
	}
	else
		return NULL;
}
//头部插入
void PushFront(SNode**Phead, DataType data)
{
	assert(Phead != NULL);
	if (*Phead == NULL)
	{
		*Phead = BuySListNode(data);
		return;
	}
	else
	{
		SNode *pNewNode = BuySListNode(data);
		pNewNode->PNext = *Phead;
		*Phead = pNewNode;

	}

}
void ReverseList1(SNode**PFirst)
{
	assert(PFirst!= NULL);
	SNode*PHead = NULL;
	SNode*Pnode = *PFirst;
	SLinkList_InIt(&PHead);
	while(Pnode!= NULL)
	{
		SNode*PNewnode = PopFront(&Pnode);//弹出第一个节点
		PushFront(&PHead, PNewnode->data);	//头插到新的链表中
	}
	*PFirst = PHead;//将新的链表赋给旧的链表
}

6.单链表的冒泡排序

冒泡排序法是最简单的排序法,这里排序链表和之前给数组排序的思路相同,这里就不再赘述了。

void ListBubbleSort(SNode**Phead)
{
	assert(Phead != NULL);
	if (*Phead == NULL)
	{
		return;
	}
	SNode*tmp = (SNode*)malloc(sizeof(SNode));
	SNode*Pnode = *Phead;
	SNode*Pcur = NULL;
	for (Pnode = *Phead; Pnode != NULL; Pnode = Pnode->PNext)
	{
		for (Pcur = Pnode->PNext; Pcur != NULL; Pcur = Pcur->PNext)
		{
			if (Pnode->data > Pcur->data)
			{
				tmp->data = Pnode->data;
				Pnode->data = Pcur->data;
				Pcur->data = tmp->data;
			}
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值