链表的插入排序、交换排序、选择排序(多图详解)

(本文中“有序区”=“顺序区”=“有序链表”,“无序区”=“乱序区”=“无序链表”,很多地方混合使用,望见谅)

能点进来这文章的肯定对链表有所认知,我就不科普了,这是我的链表类代码,用于接下来的介绍

struct Node {
	int data;
	Node* next;
};//创建结构体——结点

class LinkList {
private:
	Node* first;
public:
	LinkList();
	LinkList(int a[], int n);
	~LinkList() {};
	void swap(Node* a, Node* b, Node* c);//交换俩结点
	Node* knockout(Node* a, Node* b);//敲除链表a中的结点b(并不删除),返回新的链表首地址
	void InsertSort();
	void ExchangeSort();
	void SelectSort();
};
LinkList::LinkList() {
	first = new Node;
	first->next = nullptr;
}
LinkList::LinkList(int a[], int n) {
	first = new Node;
	Node* p = first, * q = first;
	for (int i = 0; i < n; i++) {
		p->next = new Node;
		p = p->next;
		p->data = a[i];
	}
	p->next = nullptr;
	q = q->next;//让p指向第一个结点
	cout << "链表已创建完毕,依次存储:" << endl;
	while (q != nullptr) {
		cout << q->data << '\t';
		q = q->next;
	}
	cout << '\n';
}

void LinkList::swap(Node* b, Node* c) {
	Node* p = first;
	while (p->next->data != b->data) {
		p = p->next;
	}
	p->next = c;
	b->next = c->next;
	c->next = b;
}

Node* LinkList::knockout(Node* a, Node* b) {
	Node* c = a;
	if (a->data == b->data) {
		a = a->next;
		c = a;
		b->next = nullptr;
	}//如果就是敲除头结点,那么直接指针指向下一位,第一个结点指向空
	else {//如果是中间结点,前驱直接指向后继,该结点指向为空
		while (a->next->data != b->data) {
			a = a->next;
		}//遍历完后,a就是b的前驱
		a->next = a->next->next;
		b->next = nullptr;
	}
	return c;
}

插入排序

顾名思义,把待排序的数字插入它合适的位置:在这里插入图片描述
由于链表的单向性,所以要从第一个数据开始逐一比对顺序区域在这里插入图片描述
在找到合适位置:
这里合适位置有3种情况:
理想情况:前一个小于自己,后一个大于自己
两个边际情况:
待排序数是有序中最小数,那么在第一次对比时,后一个就比它大,放在第一个位置
待排序数是有序中最大数,那么遍历完都无法插入,此时操作指针指向空,这种情况就可以判断是最大数,直接在有序区域最后放一个即可。

具体操作

初始链表是这样的:
在这里插入图片描述

需要3个工作指针,p用于指向待操作数(默认第一个结点是顺序区的)方便操作,q用于指向剩余的链表,r用于指向每次比较结点的前驱,并且在比较过程中遍历有序区域,遍历结束的标志是,符合“r的后继不小于p”这个条件,调用break,或者r指向为空,证明遍历完了
在这里插入图片描述
布置好后断开第一个结点,左边链表是有序的,后面是无序的
在这里插入图片描述
每次p指向无序区的第一个结点,用于操作其进入有序区,q指向剩余的无序链表

以这个数组为例,从第二个结点开始操作,第一个默认为有序,毕竟单独一个数字也没法说它无序

p当前指向5,,断开其与之后的无序链表的联系,以防其作为有序链表的尾巴
p(5)比r的后继(21)小,应该作为r的后继在这里插入图片描述
此轮循环结束
p赋值为q在这里插入图片描述
开始下一轮循环
相同的步骤就不说了,可以看到此时p是64,比有序区任何一个数都要大,所以不满足(r的后继不小于p),此时在这里插入图片描述
r指向已经为空,遍历有序链表的操作结束,进入判定状态,若r指向为空,则证明待插入的是有序链表数中最大的,直接插入队尾

代码实现:

void LinkList::InsertSort() {
	Node* p = first->next->next, * q = p, * r = first;

	r = r->next;
	cout << "插入排序前顺序为:" << endl;
	while (r != nullptr)
	{
		cout << r->data << '\t';
		r = r->next;
	}
	cout << '\n';

	first->next->next = nullptr;//第一个结点分离出来
	r = first;
	while (p!=nullptr) {
		q = q->next;
		p->next = nullptr;//以防p作为尾结点,其后继设为空
		while(r->next!=nullptr) {
			if (r->next->data >= p->data) {//证明q可以插入r之后
				p->next = r->next;
				r->next = p;
				break;
			}
			else {
				r = r->next;
			}
		}//遍历完成,q应该在有序链里面了
		if (r->next == nullptr) {
			r->next = p;
		}//若r都遍历完了,证明p应该作为r的尾结点
		r = first;//r恢复到初始位置,用以下一次遍历
		p = q;
	}
	r = r->next;
	cout << "排序完成,现在顺序为:" << endl;
	while (r !=nullptr)
	{
		cout << r->data << '\t';
		r = r->next;
	}
	cout << '\n';
}

测试用的主函数:

int main() {
	int a[5] = { 21,5,64,32,6 };
	LinkList l1(a, 5);
	l1.InsertSort();
}

测试结果如图
在这里插入图片描述

交换排序

这个听起来也许会陌生,但冒泡排序你肯定听过
遍历这个存储结构,交换相邻的俩数字较大(小)的,逐渐实现较小数和较大数分离,最终顺序排列
可以回顾一下数组的冒泡排序

void fun(int a[], int n) {
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n - i-1; j++) {
			if (a[j] > a[j + 1]) {
				int t = a[j];
				a[j] = a[j + 1];
				a[j + 1] = t;
			}
		}//内层for每次循环完成后,最大数会被放到数组最后面
	}//全部循环玩后,原数组就从小到大依次排列了
 }

这我就不图解了哦
那么链表的冒泡排序如法炮制

void LinkList::ExchangeSort() {
	Node* q = first,* r = first;
	int n = 0;
	r = r->next;//让r指向第一个数据
	cout << "交换排序前顺序为:" << endl;
	while (r != nullptr)
	{
		cout << r->data << '\t';
		r = r->next;
		n++;
	}
	cout << '\n';
	r = first;
	for (int j = 0; j < n; j++) {
		q = r->next;
		for (int i = 0; i < n-1&&q->next!=nullptr; i++) {//有可能交换后q为最后一个结点,则不能再进行下一步判断
			if (q->data > q->next->data) {
				swap(q, q->next);//此处函数体见开头
			}//若q和q下一个比,q大,则交换两个的位置
			else{ q = q->next; }//如果满足条件,那么q自然会移到下一个结点,不满足才需要手动移位
		}//单次循环后未排序中最大的会被放到最尾端
	}//循环结束后,链表就会有序排练

	r = first;
	r = r->next;//让r指向第一个数据
	cout << "排序后顺序为:" << endl;
	while (r != nullptr)
	{
		cout << r->data << '\t';
		r = r->next;
	}
	cout << '\n';
}

选择排序

在无序区中选择最小(大)放在有序区的结尾,构成新的有序链表在这里插入图片描述
我还是以从小到大排序为例
这次不能默认第一个是有序的,毕竟最小的不一定是第一个,应该把头结点看做有序第一个,剩余全部结点都是无序的在这里插入图片描述

p作为工作指针,遍历q指向的链表,找出最小的,放在有序区内(r用于指向有序区的尾结点,便于新的结点插入)
这里就需要用到一个“敲除”函数(开头有)来帮我们实现以上功能
整个函数的代码如下:

void LinkList::SelectSort() {
	Node* p = first->next, * q = p, * r = first;
	r = r->next;
	cout << "选择排序前顺序为:" << endl;
	while (r != nullptr)
	{
		cout << r->data << '\t';
		r = r->next;
	}
	cout << '\n';

	first->next = nullptr;//第一个结点分离出来
	r = first;
	while (q != nullptr) {
		p = q;
		Node* i = q;
		while (i != nullptr) {
			if (i->data < p->data) {
				p = i;
			}
			i = i->next;
		}//遍历完,p存储的就是无序区的最小结点
		q = knockout(q, p);
		r->next = p;
		r = r->next;
	}

	r = first->next;
	cout << "排序完成,现在顺序为:" << endl;
	while (r != nullptr)
	{
		cout << r->data << '\t';
		r = r->next;
	}
	cout << '\n';
}

测试结果如图,符合预期
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值