第一类:链表的考察(链表的插入,删除,排序和逆转等)

数据的存储结构在计算机中主要有四种基本的存储方式:顺序存储、链接存储、索引存储、散列存储方式。我们知道,顺序存储的优点是支持随机访问结构中的数据,但是在插入和删除方面运算时会造成大量数据的移动,效率较低;而链接存储却有效的补充了顺序存储的不足,当需要频繁的插入和删除时,效率得到提高。而索引存储附件了索引表,在访问时通过索引项(由关键字和地址组成值对)去访问数据的存储地址,而散列存储的就是hash的存储方式,即根据关键字通过hash转换求的数据的存储地址。在面试的过程中,链表是其中重要考察内容,下面让我们来一起学习和交流常见的链表操作。

首先,让我们重温一下单链表的数据结构:

typedef int datatype;

typedef struct ListNode{

    datatype data;//结点数据域

    ListNode *next;//结点指针域

}ListNode,*Linklist;

一、在单链表的实现的基本运算操作

这些基本操作包括:1.查找单链表中的第i个结点 2.返回指定元素的索引号 3.单链表的插入和删除,这些都是最基本的操作,大家一定要熟练,直接上码图!

*
 *author:xuzhaohu
functon:单链表的数据结构,查找、插入和删除
 */
#include <iostream>
using namespace std;

typedef int datatype;
typedef struct node
{
	datatype data;
	struct node *next;
}ListNode,*LinkList;

//查找单链表中的第i个节点的地址
ListNode * Locate(LinkList head,int i)
{
	ListNode *p;
	int index = 0;
	if(head==NULL) 
	  return NULL;
	if(i<0)
	  return NULL;
	p = head;
	if(p!=NULL && index<i)
	{
		p=p->next;
		index++;
	}
	return p;
}
//返回链表中第一个节点元素为 x 的值的索引号
int findValue(LinkList head,datatype x)
{
	ListNode *p;
	int index = 1;
	if(head==NULL)
	  return 0;
	p=head;
	if(p!=NULL)
	{
		if(p->data == x) 
		  return index;
		else
		{
			p=p->next;
			index++;
		}
	}
	else
	{
		cout<<"单链表中没有这个值的元素"<<endl;
		return 0;
	}
}
//单链表的插入 分表首插入和表中插入
void InsertNode(LinkList &head,int i,datatype x)
{
	ListNode *p,*q;
	p = Locate(head,i-1);//注意是掺入前面的一个元素
	q = new ListNode;
	q->data = x;
	if(i == 0)
	{
		q->next = head;
		head = q;
	}
	else
	{
		q = p->next;
		p->next = q;
	}
}
//单链表的删除 ,也分表头删除和表中删除
void DeleteNode(LinkList &head,int i)
{
	ListNode *p,*q;//q是删除节点p是其前一个节点
	p = Locate(head ,i-1);//注意是找到前面一个元素
	if(i==0)
	{
		q = head;
		head=q->next;
		delete q;
	}
	else
	{	if(p==NULL)//i值给的有问题,索引值溢出后者为负值
			cout<<"error"<<endl;			
	    else if(p->next == NULL)//p恰好是最后一个元素
		  	cout<<"no ith node"<<endl;		
		else
		{
			q = p->next;
			p->next = q->next;
			delete q;
		}
	}
}

int main()
{
	return 0;			
}


二、用链表实现对栈和队列

这是个基础也是非常重要的算法实现问题,既能考察对栈和队列的理解又考察了对链表的操作,因此,也是面试中应该着重学习的地方。

2.1 链栈的数据操作

/**
 * author:xuzhaohu
 * function:stackNode 先进后出
 * date:
 */
#include<iostream>
using namespace std;
//与单链表相似,不同之处在与插入和删除只限定在表首进行,表头指针就是栈顶元素 
typedef int datatype;
typedef struct node
{
	datatype data;
	struct node *next;
}StackNode,*LinkStack;
// 链栈的插入 
void push(LinkStack &top,datatype x)
{
	StackNode *p;
	p = new StackNode;
	//注意:链栈的插入只考虑单链表的表首插入 
	p->data = x;
	p->next = top;
	top = p;
}
//链栈的弹出 
void pop(LinkStack &top,datatype &x)
{
	StackNode *p;
	if(top == NULL)
		cout<<"underflow"<<endl;
	else
	{
		//链栈的删除只考虑表首的删除 
		p = top;
		x = top->data;
		top = top->next;
		delete p;
	}
}
//读取链栈的栈顶元素 
void GetTop(LinkStack top, datatype &x)
{
	if(top == NULL)
	  cout<<"error";
	else
	  x = top->data;
}
//置空链栈 
void ClearStack(LinkStack &top)
{
	top = NULL;
}
//判断链栈是否为空 
int StackEmpty(LinkStack &top)
{
	if(top == NULL)
	  return 1;
	else
	  return 0;
}

int main()
{
	return 0;
}

2.2链队列的数据操作

/**
 *author:xuzh
 *function:链队列的数据结构,先进先出
 */
#include<iostream>
using namespace std;

typedef int datatype;
typedef struct node
{
	datatype data;
	struct node *next;
}QueueNode;
typedef struct
{
	QueueNode *front;//队头指针
	QueueNode *rear;//队尾指针
}LinkQueue;
QueueNode *p,*q;
LinkQueue QU;
datatype x;

//链队列的插入,只能在队尾插入
void EnQueue(LinkQueue &QU,datatype x)
{
	QueueNode *p = new QueueNode;
	p->data = x;
	p->next = NULL;//p为最后一个元素
	if(QU.front == NULL)
	  QU.front = QU.rear = p;
	else
	{
		QU.rear->next = p;//队尾元素变为新的元素p,p为旧的队尾元素的下一个元素
		QU.rear = p;
	}
}
//链队列的删除,只能在队首操作
void DeQueue(LinkQueue &QU,datatype &x)
{
	QueueNode *q;
	if(QU.front == NULL)
	  cout<<"underflow"<<endl;
	else	
	{
		q = QU.front;
		x = q->data;
		QU.front = q->next;
		delete q;
	}
}
//读栈头元素
void GetFront(LinkQueue &QU,datatype &x)
{
	if(QU.front == NULL)
	  cout<<"error"<<endl;
	else
	  x = QU.front->data;
}
//清空队列
void ClearQueue(LinkQueue &QU)
{
	QU.front = QU.rear = NULL;
}
//判定一个队列是否为空
int QueueEmpty(LinkQueue &QU)
{
	if(QU.front == NULL)
	  return 1;
	else
	  return 0;
}
int main()
{
	return 0;
}

三、链表的综合操作

3.1.逆向链表的实现 ReverseList(ListNode &head)

思路:通过改变指针指向实现,代码如下:

//reverse the list
void ReverseList(LinkList &head)
{
	ListNode *p,*q,*r;//相邻的两个指针
	if(head == NULL)
	  cout<<"error"<<endl;
	p = head;
	q = p->next;
	p->next = NULL;//首节点对应着 逆转后的 最后一个节点
	while(q!=NULL)
	{
		if(q->next==NULL)	
			head = q;//表首指针指向最后一个元素
		r = q->next;
		q->next = p;
		p = q;
		q = r;	
	}	
}

3.2尽量用最小的时间消耗完成对中间结点的查询

思路:用快慢指针实现,快指针步长为2,慢指针步长为1,同时遍历,当快指针遍历完成时,慢指针指向就是中间结点,复杂度为O(n),代码如下

//尽量用最小的时间消耗完成对中间结点的查询
void FindMindleNode(LinkList head)
{
	ListNode *fast,*slow;//定义一个快慢指针,一个步长为2,一个步长为1
	int index = 1;//中间节点的索引值
	if(head == NULL)
	{
	  cout<<"error"<<endl;
	  return ;
	}
	slow = fast = head;
	if(head->next == NULL)
	{
	  cout<<"the mid node is "<<index<<endl;
	  return ;
	} 
	slow = slow->next;//慢指针走一步
	fast = fast->next;//快指针走两步
	while(fast!=NULL && slow!=NULL)
	{
		index++;
		slow = slow->next;
		if(fast->next == NULL)//如果fast的下一个指针就已经为空了,说明快指针已经遍历完成
			fast = fast->next;//这时候让fast指针指向最后一个元素就行
		else
			fast = fast->next->next;
	}
	cout<<"the mid node is "<<index<<endl;
}

3.3用最小空间和时间找出该链表的倒数第m个元素

思路:同样可以采用双指针的,一个指针先遍历m个元素,然后第二个指针与第一个指针同时遍历链表,直到第一个指针遍历结束,则第二个指针遍历到的位置即为倒数第m个元素。代码如下

//get the mth element in the list
int GetLastM(LinkList head, int m)
{
	ListNode *fast,*slow;
	int i = 0;
	fast = slow = head;
	if(head == NULL)
	  return -1;
	while(fast!=NULL)
	{
		i++;
		fast = fast->next;
		if(i>=m)
		{
			if(slow!=NULL)
			  slow = slow->next;
		}
	}
	return slow->data;
}

3.4如何判断一个单向链表是否存在循环

思路:一、使用步长法判断,用不同步长遍历两次,看是否遇见。遇见则证明有环;二、如果链表中当前的node的下一个node不是前面的任何一个访问后的node,那么则没有环,否则有环。下面是思路一的代码

// Circle List 's last element->next = head
bool CircleList(LinkList head)
{
	//define two different step pointer,if they meet,there is circle, or not
	ListNode *fast,*slow;
	if(head == NULL)
	  return false;
    slow = head;
	fast = head->next;
	while(fast!=NULL && fast->next!=NULL && slow!=fast)
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	if(slow = fast)
	  return true;
	else
	  return 0;
}

结束!小弟也在学习中,希望通过自己学到的和大家进行交流,如有不准确和正确的地方,请各位指正,小弟不甚感激!!^_^



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值