单链表的创建、添加、删除+判断是否有环、环的长度、环入口

14 篇文章 0 订阅
2 篇文章 0 订阅

单链表

       单链表是数据节点是单向排列。包括两个域:数据域和指针域。单链表的节点数据结构如下:

//  单链表节点数据结构
class listNode
{
public:
	int value;       //  数据域
	listNode *next;  //  指针域
};

        通常用“头指针”来标识一个单链表,头指针是指向单链表第一个节点的指针,如图1所示表head(head为头指针)。nullptr==head为空链表

       此外,为了方便操作,可以在单链表第一个节点之前附加一个节点,称为头节点,如图2所示。头节点的指针域指向链表的第一个节点,数据域可以不设任何信息。带头结点的单链表headNode为空的判定条件为nullptr==headNode

单链表的建立

       单链表的建立可以用头插法和尾插法。头插法是指建立单链表时,总是将新节点插入到当前链表的表头,通常用在将一个已存在的链表逆序。尾插法创建单链表代码如下:链表长度为6,节点值分别为1,2,3,4,5,6。

//  创建一个单链表(尾插法)
listNode* createList()
{
	listNode *head = new listNode;
	listNode *headNode = head;
	if (headNode != nullptr)
	{
		for (int i = 1; i < 7; i++)
		{
			listNode *newNode = new listNode;
			newNode->value = i;
			newNode->next = nullptr;
			headNode->next = newNode;
			headNode = newNode;
			headNode->next = nullptr;
		}
		return head->next;
	}
	return nullptr;
}

//  遍历单链表
void printList(listNode *head)
{
	listNode *p = head;
	while (p != nullptr)
	{
		cout << p->value << " ";
		p = p->next;
	}
	cout << endl;
}
int main()
{
	listNode *head = createList();
	printList(head);
        //  输出:1 2 3 4 5 6
	return 0;
}

单链表的插入

       向单链表的尾部插入一个值为val的节点:

       (1) 链表是否为空;

       (2) 链表非空时找到尾节点,插入值为val的节点;

       (3) 当向空链表中插入新的节点会改动头指针,需要使用引用形参或者二维指针形参,否则除了这个函数仍然是一个空指针。

// 向单链表的尾部添加一个值为val的节点
void addtoTail(listNode *&head,int val)  //  head形参为引用类型
{
	listNode *newNode = new listNode;
	newNode->value = val;
	newNode->next = nullptr;
	listNode *p = head;
	if (nullptr == head)   //  链表为空时
	{
		head = newNode;
	}
	else                   //  链表非空时
	{
		listNode *p = head;
		while (p->next != nullptr) //  找到尾节点的前驱节点
		{
			p = p->next;
		}
		p->next = newNode;  //  插入新节点
	}
}


int main()
{
	listNode *head = createList();
	addtoTail(head, 7);
	printList(head);
	//  输出:1,2,3,4,5,6,7
	return 0;
}

单链表的删除

       从单链表中删除一个值为val的节点:

       (1) 链表是否为空;

       (2) 删除的是首节点;

       (3) 删除的是非首节点;

       (4) 当所删除的节点是首节点时,会改动头指针,需要使用引用形参或者二维指针形参,否则会输出垃圾值,并显示编译错误。

//  从单链表中删除一个值为val的节点
void deleteNode(listNode *&head, int val)  //  head形参为引用类型
{
	if (nullptr == head)             //  链表为空
		return;
	listNode *deleteNode = nullptr;  //  记录要删除的节点
	if (head->value == val)          //  删除的是首节点
	{
		deleteNode = head;
		head = head->next;
	}
	else                             //  删除的是非首节点
	{
		listNode *p = head;
		while (p->next != nullptr && p->next->value != val)
		{
			p = p->next;            //  找到要删除的节点的前驱节点
		}
		if (p->next != nullptr && p->next->value == val)
		{
			deleteNode = p->next;   //  记录
			p->next = p->next->next;//  删除值为val的节点
		}
	}
	if (deleteNode != nullptr)
	{
		delete deleteNode;  
		deleteNode = nullptr;
	}
}

创建一个带环的单链表

      创建一个带环的单链表,链表长度为6,环入口节点的值为3,代码如下:

//  创建一个带环的单链表,链表长度为6,环入口节点值为3
listNode* createListwithLoop()
{
	listNode *head = new listNode;
	listNode *headNode = head;
	listNode *pSave = nullptr;     //  定义一个节点指针,用于创建环
	if (headNode != nullptr)
	{
		for (int i = 1; i < 7; i++)
		{
			listNode *newNode = new listNode;  //  创建新节点
			newNode->value = i;      //  初始化数据域
			newNode->next = nullptr; //  初始化数据域
			headNode->next = newNode;//  头节点与新节点相链接,即:将新节点作为头节点的后继
			headNode = newNode;      //  头节点前进一步,作为即将创建的新节点的前驱,即为尾插法,不断从尾部插入新节点
			headNode->next = nullptr;//  清空指针域,为链节下一个新节点做准备
			if (i == 3)
			{
				pSave = newNode;     //  保存第三个节点的指针,为在第三个节点处创建环做准备
			}
		}
		headNode->next = pSave;      //  将尾指针的next指向第三个节点,即:创建了一个有环的链表,环入口在第三个节点处
		return head->next;
	}
	return nullptr;
}

判断单链表是否有环

       快慢指针中的快慢是指移动的步长,及每次向前移动的快慢。如:可以让快指针每次沿链表向前移动2,慢指针每次向前移动1

       思路:使用快慢指针,让快慢指针均从链表头开始遍历,快指针每次向前移动两个位置,慢指针每次向前移动一个位置;如果快指针到达nullptr,说明链表以nullptr结尾,没有环;如果快指针追上慢指针,则表示单链表有环。设链表有环时快慢指针的相遇点为entryNode。码如下:

//  单链表的环
//  使用快慢指针:快指针走两步,慢指针走一步,快慢指针相遇则有环

//  找快慢指针相遇点,有则返回,无则返回nullptr;
listNode* getMeetNode(listNode *head)
{
	listNode *p1 = head;
	listNode *p2 = head;
	while (p2 != nullptr && p2->next != nullptr)
	{
		p1 = p1->next;
		p2 = p2->next->next;
		if (p1 == p2)
			return p2;
	}
	return nullptr;
}

//  判断链表是否有环
void hasLoop(listNode *head)
{
	if (getMeetNode(head))
		cout << "链表有环" << endl;
	else
		cout << "链表无环" << endl;
}


int main()
{
	listNode *head = createListwithLoop();
	hasLoop(head);
	//  输出:链表有环
	return 0;
}

求单链表中环的长度

       思路:找到快慢指针的相遇点entryNodeentryNode沿着链表的环走一周(每次一步),再次回到相遇点,走的步数即为环的长度。代码如下:

//  环的长度
int getLoopLen(listNode *head)
{
	if (nullptr == head)
		return 0;
	listNode *meetNode = getMeetNode(head);
	listNode *p = meetNode;
	int cnt = 1;
	while (p->next != meetNode)
	{
		cnt++;
		p = p->next;
	}
	return cnt;
}

int main()
{
	listNode *head = createListwithLoop();
	cout << "环的长度为" << getLoopLen(head) << endl;
	//  输出:环的长度为4
	return 0;
}

找单链表中环的入口节点

       思路:环入口点到起始点的距离a和相遇点到环入口点的距离(r-X)相差环长度r整数倍。

       假设链表存在环,链表长度为L,起始点到换入口的距离为a,环长度为r,则L=a+r;如图3所示。在快指针进入环到慢指针进入环前的这段时间,如环的长度较短,也许快指针已经在环中走了n圈,然后慢指针再进入环。设慢指针和快指在环中相遇时,慢指针在环中走了X步,则快、慢指针走的总步数KM分别为K=a+nr+XM=a+X。由K=2M得:a+nr+X=2(a+X),即:a=(n-1)r+r-X

        上式含义为:环入口点到起始点的距离a和相遇点到环入口点的距离(r-X)相差环长度r整数倍。

       所以让慢指针回到起始点,快指针从相遇点起,两者同时出发,且步长均为1,则当两者相遇时,即为环入口节点。代码如下:

//  找环的入口节点
listNode *getEntryNode(listNode *head)
{
	listNode *p1 = getMeetNode(head);
	if (nullptr == p1)
		return nullptr;
	listNode *p2 = head;
	while (p1 != p2)
	{
		p1 = p1->next;
		p2 = p2->next;
	}
	return p1;
}
int main()
{
	listNode *head = createListwithLoop();
	listNode *entryNode = getEntryNode(head);
	cout << "环的入口节点值为" << entryNode->value << endl;
	//  输出:环的入口节点值为3
	return 0;
}

求单链表的长度

       由上述可知,链表长度L=a+r,环的长度已求得,只需求起始点到环入口的距离a。让慢指针从起始点出发,到达环入口时,走的步长即为a。代码如下:

//  单链表的长度
int getListLen(listNode *head)
{
	if (nullptr == head)
		return 0;
	listNode *entryNode = getEntryNode(head);
	listNode *p = head;
	int cnt = 0;
	while (p != entryNode)
	{
		cnt++;
		p = p->next;
	}
	int len = cnt + getLoopLen(head);
	return len;
}

int main()
{
	listNode *head = createListwithLoop();
	cout << "单链表的长度为" << getListLen(head) << endl;
	//  输出:单链表的长度为6
	return 0;
}

上述代码汇总

#include<iostream>

using namespace std;

//  单链表节点数据结构
class listNode
{
public:
	int value;
	listNode *next;
};

//  遍历单链表
void printList(listNode *head)
{
	listNode *p = head;
	while (p != nullptr)
	{
		cout << p->value << " ";
		p = p->next;
	}
	cout << endl;
}
//  创建一个单链表(尾插法)
listNode* createList()
{
	listNode *head = new listNode;
	listNode *headNode = head;
	if (headNode != nullptr)
	{
		for (int i = 1; i < 7; i++)
		{
			listNode *newNode = new listNode;
			newNode->value = i;
			newNode->next = nullptr;
			headNode->next = newNode;
			headNode = newNode;
			headNode->next = nullptr;
		}
		return head->next;
	}
	return nullptr;
}

// 向单链表的尾部添加一个值为val的节点
void addtoTail(listNode *&head,int val)
{
	listNode *newNode = new listNode;
	newNode->value = val;
	newNode->next = nullptr;
	listNode *p = head;
	if (nullptr == head)
	{
		head = newNode;
	}
	else
	{
		listNode *p = head;
		while (p->next != nullptr)
		{
			p = p->next;
		}
		p->next = newNode;
	}


}

//  从单链表中删除一个值为val的节点
void deleteNode(listNode *&head,int val)
{
	if (nullptr == head)
		return;
	listNode *deleteNode = nullptr;
	if (head->value == val)
	{
		deleteNode = head;
		head = head->next;
	}
	else
	{
		listNode *p = head;
		while (p->next != nullptr && p->next->value != val)
		{
			p = p->next;
		}
		if (p->next != nullptr && p->next->value == val)
		{
			deleteNode = p->next;
			p->next = p->next->next;
		}
	}
	if (deleteNode != nullptr)
	{
		delete deleteNode;
		deleteNode = nullptr;
	}
}

//  创建一个带环的单链表,链表长度为6,环入口节点值为3
listNode* createListwithLoop()
{
	listNode *head = new listNode;
	listNode *headNode = head;
	listNode *pSave = nullptr;     //  定义一个节点指针,用于创建环
	if (headNode != nullptr)
	{
		for (int i = 1; i < 7; i++)
		{
			listNode *newNode = new listNode;  //  创建新节点
			newNode->value = i;      //  初始化数据域
			newNode->next = nullptr; //  初始化数据域
			headNode->next = newNode;//  头节点与新节点相链接,即:将新节点作为头节点的后继
			headNode = newNode;      //  头节点前进一步,作为即将创建的新节点的前驱,即为尾插法,不断从尾部插入新节点
			headNode->next = nullptr;//  清空指针域,为链节下一个新节点做准备
			if (i == 3)
			{
				pSave = newNode;     //  保存第三个节点的指针,为在第三个节点处创建环做准备
			}
		}
		headNode->next = pSave;      //  将尾指针的next指向第三个节点,即:创建了一个有环的链表,环入口在第三个节点处
		return head->next;
	}
	return nullptr;
}


//  单链表的环
//  使用快慢指针:快指针走两步,慢指针走一步,快慢指针相遇则有环

//  找快慢指针相遇点,有则返回,无则返回nullptr;
listNode* getMeetNode(listNode *head)
{
	listNode *p1 = head;
	listNode *p2 = head;
	while (p2 != nullptr && p2->next != nullptr)
	{
		p1 = p1->next;
		p2 = p2->next->next;
		if (p1 == p2)
			return p2;
	}
	return nullptr;
}

//  判断链表是否有环
void hasLoop(listNode *head)
{
	if (getMeetNode(head))
		cout << "链表有环" << endl;
	else
		cout << "链表无环" << endl;
}


//  找环的入口节点
listNode *getEntryNode(listNode *head)
{
	listNode *p1 = getMeetNode(head);
	if (nullptr == p1)
		return nullptr;
	listNode *p2 = head;
	while (p1 != p2)
	{
		p1 = p1->next;
		p2 = p2->next;
	}
	return p1;
}
//  环的长度
int getLoopLen(listNode *head)
{
	if (nullptr == head)
		return 0;
	listNode *meetNode = getMeetNode(head);
	listNode *p = meetNode;
	int cnt = 1;
	while (p->next != meetNode)
	{
		cnt++;
		p = p->next;
	}
	return cnt;
}

//  单链表的长度
int getListLen(listNode *head)
{
	if (nullptr == head)
		return 0;
	listNode *entryNode = getEntryNode(head);
	listNode *p = head;
	int cnt = 0;
	while (p != entryNode)
	{
		cnt++;
		p = p->next;
	}
	int len = cnt + getLoopLen(head);
	return len;
}
int main()
{
	cout << "无环单链表的遍历、插入、删除" << endl;
	cout << "----------------------------------------------" << endl;
	listNode *head = createList();
	printList(head);
	addtoTail(head, 7);
	printList(head);
	deleteNode(head, 1);
	printList(head);
	cout << "----------------------------------------------" << endl;
	cout << "带环单链表的判断、入口节点、环长度、链表长度" << endl;
	listNode *headNode = createListwithLoop();
	hasLoop(headNode);
	listNode *entryNode = getEntryNode(headNode);
	cout << "环的入口节点值为" << entryNode->value << endl;
	cout << "环的长度为" << getLoopLen(headNode) << endl;
	cout << "单链表的长度为" << getListLen(headNode) << endl;
	cout << "----------------------------------------------" << endl;
	return 0;
}

运行结果:

               

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值