(本文中“有序区”=“顺序区”=“有序链表”,“无序区”=“乱序区”=“无序链表”,很多地方混合使用,望见谅)
能点进来这文章的肯定对链表有所认知,我就不科普了,这是我的链表类代码,用于接下来的介绍
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';
}
测试结果如图,符合预期