链表的操作

链表的操作一直是困扰着本人,于是还是自己总结一番,针对性的加强训练。

下面是自己的一点点总结:

链表其实就是多个数据块通过指针连接起来,所谓的链表的操作,最核心也是最困难的就是调整指针的指向,每个结点只能也只有它能知道下一个结点的存储位置。改变链表的顺序就是不断的调整每个数据块的指针指向。因此,除非需要创建新的结点,会使用直接创建Node结点,否则一般都是创建Node类型的指针,通过指针不断的操作每个数据块中指针的指向,从而达到操作链表顺序的目的。


一些链表的复杂操作也基本上是由上面的操作组合构成。

链表主要分为:

单链表:单链表每个节点只包含一个后驱指针

双链表:双链表节点同时包含一个前驱指针和一个后驱指针

循环链表:循环链表的尾节点的后驱指向头节点

此外还需要注意的是:单链表还包括带头节点与不带头结点的两种。两者的差别不大,主要是为了代码操作的方便。

链表的创建:

单链表结点定义:

typedef struct Lnode
{ 
	ElemType data; /*数据域,保存结点的值 */
	struct Lnode *next; /*指针域*/
}LNode; /*结点的类型 */

创建单链表:

头插入法:从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头上,直到读入结束标志为止。即每次插入的结点都作为链表的第一个结点。生成的链表中结点的次序和输入的顺序相反。

尾插入法:将新结点插入到当前链表的表尾,使其成为当前链表的尾结点。生成的链表中结点的次序和输入的顺序相同。

  • 使用头插入法创建带头结点的单链表:


LNode* CreatLinkList()
{
	LNode *head = new LNode;//头结点包含实实在在的数据域,并不是一个空指针。
	head->next = NULL;
	for (int i=0;i<100;i++)
	{
		LNode* TempNode = new LNode;
		TempNode->data = i;
		TempNode->next = head->next;
		head->next = TempNode;
	}
	return head;
}

  • 使用头插入法创建不带头结点的单链表:

LNode* CreatLinkListNoHead()
{
	LNode* head = NULL;
	for (int i=0;i<100;i++)
	{
		LNode* TempNode = new LNode;
		TempNode->data = i;
		if(NULL == head)
		{
			head = TempNode;
			head->next = NULL;
		}
		else
		{
			TempNode->next = head;
			head = TempNode;
		}
	}
	return head;
}
//下面的方法也可以
LNode* CreatLinkListNoHead1()
{
	LNode* head = NULL;
	for (int i=0;i<100;i++)
	{
		LNode* TempNode = new LNode;
		TempNode->data = i;
		TempNode->next = head;
		head = TempNode;
	}
	return head;
}
  • 使用尾插法创建带头结点的单链表:

LNode* CreatLinkListTail()
{
	LNode* head = new LNode;
	LNode* CurNode = head;
	CurNode->next = NULL;
	for (int i=0;i<100;i++)
	{
		LNode* TempNode = new LNode;
		TempNode->data = i;
		TempNode->next = CurNode->next;
		CurNode->next = TempNode;
		CurNode = TempNode;
	}
	return head;
}

  • 使用尾插法创建不带头结点的单链表:

LNode* CreatLinkListTailNoHead()
{
	LNode* head = NULL;
	LNode* CurNode = head;
	for (int i=0;i<100;i++)
	{
		LNode* TempNode = new LNode;
		TempNode->data = i;
		if (NULL == head)
		{
			head = CurNode = TempNode;
			TempNode->next = NULL;
		}
		else
		{
			TempNode->next = CurNode->next;
			CurNode->next = TempNode;
			CurNode = TempNode;
		}
	}
	return head;
}
LNode* CreatLinkListTailNoHead()
{
	LNode* head = NULL;
	LNode* CurNode = head;
	for (int i=0;i<100;i++)
	{
		LNode* TempNode = new LNode;
		TempNode->data = i;
		TempNode->next = NULL;
		if (head == NULL)
		{
			head=CurNode = TempNode;
		}
		else
		{
			CurNode->next = TempNode;
			CurNode = TempNode;
		}
	}
	return head;
}

对于单链表,无论是哪种操作,只要涉及到钩链(或重新钩链),如果没有明确给出直接后继,钩链(或重新钩链)的次序必须是“先右后左”。

当一个指针为NULL时,千万不要使用此指针进行操作,一定要进行判断,例如

LNode* head = NULL;
	head->next = NULL;
就是大错特错的。

此外通过上述操作可以发现,不带头结点的时候需要对头结点为空的情形进行特殊处理。带有头结点的单链表只不过浪费了一个结点的数据存储域,(不一定是浪费,这个数据域可用来存储一些需要的数据,例如单链表的长度等)在操作的时候不需要判断头结点为空的情形,代码更加简洁有效。头结点的数据域可以不存储任何信息,头结点的指针域存储指向第一个结点的指针(即第一个元素结点的存储位置)。头结点的作用是使所有链表(包括空表)的头指针非空,并使对单链表的插入、删除操作不需要区分是否为空表或是否在第一个位置进行,从而与其他位置的插入、删除操作一致。


单链表的插入操作:

  • 带有头结点的单链表插入操作:

bool InsertValue(LNode* head,int Index,int KeyValue)
{
	if (head == NULL || Index < 0)
	{
		return false;
	}
	
	LNode *CurNode = head;
	int i = -1;
	while (CurNode!=NULL && i<Index-1)
	{
		CurNode= CurNode->next;
		i++;
	}
	if (i==Index-1)
	{
		LNode* TempNode  = new LNode;
		TempNode->data = KeyValue;
		TempNode->next = CurNode->next;
		CurNode->next = TempNode;
		return true;
	}
}

插入时一定要考察参数的有效性,其中就包括Index的合法性等,同时插入操作需要问清楚面试官的要求,比如插入位置为0时是不是插入第一个结点。这牵涉到计数器i的初始值。

  • 不带头结点的单链表的插入操作:

bool InsertValueNohead(LNode*& head,int Index,int Key)
{
	if (head ==NULL || Index <0)
	{
		return false;
	}
	if (Index == 0)
	{
		LNode* TempNode = new LNode;
		TempNode->data = Key;
		TempNode->next = head;
		head = TempNode;
		return true;
	}
	LNode * CurNode = head;
	int i = 0;
	while (CurNode!=NULL && i<Index-1)
	{
		CurNode= CurNode->next;
		i++;
	}
	if (i==Index-1)
	{
		LNode* TempNode  = new LNode;
		TempNode->data = Key;
		TempNode->next = CurNode->next;
		CurNode->next = TempNode;
		return true;
	}
	return false;
	
}

上述代码中需要注意的是:

  1. 需要判断插入位置是否是头结点,如果是头结点的话需要单独操作,这就是与带头结点单链表的区别,因为带头结点的单链表的第一个结点始终都不会改变,而不带头结点的单链表的第一个结点有可能被改变。
  2. 头结点需要改变的时候,由于返回值是bool类型,因此在传值的时候使用了引用类型。否则会没有效果。

单链表的删除操作: 

单链表的删除操作可以分为:按序号删除结点,按值删除结点

按序号删除:

带头结点的按序号删除:

bool DeleteNode(LNode* head,int Index)
{
	if (head == NULL || Index <= 0)
	{
		return false;
	}
	LNode * CurNode = head;
	int i=0;
	while (CurNode!=NULL && i<Index-1)
	{
		CurNode = CurNode->next;
		i++;
	}
	if (i != Index-1)
	{
		return false;
	}
	LNode* p = CurNode->next;
	CurNode->next = p->next;
	delete p;
	return true;
}

不带头结点的单链表按序号删除:

bool DeleteNodeNoHead(LNode*& head,int Index)
{
	if (head==NULL || Index<=0)
	{
		return false;
	}
	if (Index == 1)
	{
		LNode* Node = head;
		head = head->next;
		delete Node;
	}
	else
	{
		LNode * CurNode = head;
		int i=1;
		while (CurNode!=NULL && i<Index-1)
		{
			CurNode = CurNode->next;
			i++;
		}
		if (i != Index-1)
		{
			return false;
		}
		LNode* p = CurNode->next;
		CurNode->next = p->next;
		delete p;
		return true;
	}
}

带头结点按值删除操作:

bool DeleteNodeKey(LNode* head, int key)
{
	if (head == NULL)
	{
		return false;
	}
	LNode* CurNode  = head;
	LNode* TempNode = NULL;
	while ( CurNode->next!=NULL && CurNode->next->data != key)
	{
		CurNode = CurNode->next;
	}
	if (CurNode->next->data == key)
	{
		TempNode = CurNode->next;
		CurNode->next= TempNode->next;
		delete TempNode;
		return true;
	}
	return false;
}
不带头结点的按值删除操作

bool DeleteNodeKeyNoHead(LNode*& head,int key)
{
	if (head == NULL)
	{
		return false;
	}
	if (head->data == key)
	{
		LNode* Node = head;
		head = Node->next;
		delete Node;
		return true;
	}
	else
	{
		LNode* CurNode  = head;
		LNode* TempNode = NULL;
		while ( CurNode->next!=NULL && CurNode->next->data != key)
		{
			CurNode = CurNode->next;
		}
		if (CurNode->next->data == key)
		{
			TempNode = CurNode->next;
			CurNode->next= TempNode->next;
			delete TempNode;
			return true;
		}
		return false;
	}
	
}
关于链表的插入或者删除操作,都要首先找到要删除结点的前一个结点。因为每个结点所包含的的链接信息只是它的下一个结点,其余的信息一概不知。












评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值