【SUFE】数据结构作业——排序(自然二路归并排序算法, 基数排序)

原文地址

第一题

题目

已知一个数组中存放了一个字符串如:str[]=”abcdefg”;根据字符串的内容建立一个单向链表,并输出该链表,然后把原链表逆转成一个新的链表,并输出逆转后的新链表。

node {
    char  data;
    node  *link 
};
node  *create_link(char a[]){}
void Out_link(node *head){}
node  *reverse_link(node *head){}

思路

  1. 首先是根据字符串建立链表,我们直接一个一个取char数组的值,创建新的链表结点即可。
  2. 接着是反转链表,对于不到2个结点的情况,我们直接反转。对于>=2个结点的情况,我们可以创建三个指针指向连续的三个结点(第三个可以为NULL)。
    将第二个指针的link指向第一个指针指向的结点,然后根据第三个指针所保存的信息(没有它就会与后面部分的链表断连),将这三个指针整体向后移动一位,直到全部反转完

源程序清单

#include<iostream>
using namespace std;

struct node {
    char  data;
    node* link;
};

//这是根据字符串创建链表的函数
node* create_link(char a[]){
    if (a[0] == '\0') return NULL;

    node* head = new node;
    head -> data = a[0]; head->link = NULL;
    node* p = head, * q;	//p:指向尾结点,q:创建新结点

    int i = 1;

    while (a[i] != '\0') {
        q = new node;
        q->data = a[i]; q->link = NULL;	//建新结点
        p->link = q; p = q;	//处理旧结点
        i++;
    }
    return head;
}

//这是根据传入的头结点输出链表的函数
void Out_link(node* head){
    node* p = head;
    while (p != NULL) {
        cout << p->data << " -> ";
        p = p->link;
    }
    cout << "\n";
}

//这是反转链表的函数
node* reverse_link(node* head)
{
    if (head == NULL || head->link == NULL) return head; //不多于2个结点

    node* left = head, * mid = head->link, * right = mid->link;	//三个变量分别表示左中右结点(包含NULL)
    head->link = NULL;  //这里的head变成尾指针了

    while (right != NULL) {
        mid->link = left;	//反转,将第二个指针指向的结点接到第一个指向的上面
        left = mid; mid = right; right = right->link;	//整体后移一位
    }
    mid->link = left;   //最后right == NULL,所以还差最后两个结点没有接上去,此时mid为头结点 
    return mid;
}

int main() {
    char s[] = "abcdefg";
    node* head = create_link(s);
    Out_link(head);

    head = reverse_link(head);
    Out_link(head);
    return 0;
}

第二题(略)

题目

已知数据如下:
20 10 90 99 40 50 30 60 80 70
请写出分别用插入排序、选择排序、冒泡排序的、合并排序、快速排序的每趟处理后的数据清单。

第三题

题目

实现如下算法:以单链表为存储结构实现简单选择排序的算法。
链表结点结构: struct nofr {int data; nofe *link };

思路

  1. 首先,选择排序是找到某一段数据中的最小值,然后把它和开头元素交换。
  2. 如果是数组,我们可以直接用双重for循环做。到了链表这里,处理稍微有点麻烦,但是总体思路是一样的。
  3. 首先,我们需要一个指针指向“开头”,这个开头指未排好序的开头,便于与最小值交换(交换后就属于排好序的那部分了)。
    既然要找到最小值,那么我们就要有一个指针能去遍历“开头”后面的结点;还得有一个指针去指向目前最小值的结点,便于比较和交换。
  4. 遍历完成后,将最小值与“开头”交换,然后将“开头”往后移动一位,就可以重复下去了。

源程序清单

#include <iostream>
using namespace std;

struct node {
    int data;
    node* link;
};

//选择排序
void selectionSort(node *head) {
    node* p = head;//p用于往后遍历每一个元素
    node* q = head;	//q用于指向排好序的末尾的结点的后一个结点
    node* t = head;	//t用于指向最小元素

    p = q->link;
    while (q->link != NULL && p != NULL) {
        t = q;	//从q开始
        while (p != NULL) {	//往后遍历所有结点
            if (p->data < t->data) {	//如果找到了更小的元素
                t = p;//t指向最小元素
            }
            p = p->link;//继续向后遍历
        }//此时已经找到了最小的值所在的结点,由t指向

        if (t != q) {//最小值在后面的话,就交换两个结点的值
            /*int tem = t->data;
            t->data = q->data;
            q->data = tem;*/
            swap(t->data, q->data);
        }
        q = q->link;//排好序后,更新末尾结点
        p = q->link;//因为t指向q,所以p从q的下一个开始
    }
}

//此函数用于创建随机单链表
node* createRandLink()
{
    srand((unsigned int)time(NULL));
    node* head = NULL, * p, * q;
    int n = rand() % 20 - 10;   
    head = new node;
    p = head;
    p->data = n;
    p->link = NULL;

    for (int i = 1; i < 10; i++) {
        q = new node;
        q->data = rand() % 40 - 20;  //随机数
        q->link = NULL;
        p->link = q;
        p = q;
    }
    return head;
}

//此函数用于输出链表
void outPutLink(node *head) {
    node* p = head;
    while (p != NULL) {
        cout << p->data << " -> ";
        p = p->link;
    }
    cout << endl;
}

int main() {
    node* head = createRandLink();
    outPutLink(head);
    selectionSort(head);
    outPutLink(head);
}

第四题

题目

直接选择排序算法是不稳定的排序算法。这主要是由于每次从无序记录区选出最小记录后,与无序区的第一个记录交换而引起的,因为交换可能引起关键字相同的数据元素位置发生变化。如果在选出最小记录后,将它前面的无序记录依次后移,然后再将最小记录放在有序区的后面,这样就能保证排序算法的稳定性。编写一个稳定的直接选择排序算法。

思路

  1. 题目说得很清楚,直接将“替换”改为“后移”即可。

源程序清单

//int a[]为传入的数组,int n为数组长度
void dirSelSort(int a[], int n) {
    int i = 0;	//i用于指向排好序的末尾
    int search = 1;	//用于遍历后面的内容
    int min = 0;	//最小值位置
    for (; i < n; i++) {
        search = i + 1;
        min = i;
        while (search < n) {//找到最小值编号
            if (a[search] < a[min]) {
                min = search;
            }
            search++;
        }//找到了最小值编号,跟search值无关了

        //开始替换
        int tem = a[min];
        while (min > i) {
            a[min] = a[min - 1];	//每一个都后移一位
            min--;
        }
        a[i] = tem;
    }
}

第五题

题目

实现自然二路归并排序算法,算法思想如下 ,从左到右扫描,扫描到二路已经有序的自然数据段,把扫描到的二路合并成一路有序的数据段,继续对后续的数据进行扫描,同样对二路有序数据合并成一路有序的,如最后只剩下一路有序的数据就照抄下来,这样经过一趟全部数据的合并后,整个数据更加有序了,不断重复上述过程,只到全部数据有序。

思路

  1. 这是自然归并排序算法(Natural Merge Sort)
  2. 最首先的,是要找到哪些数据片段是有序的。以升序为例,一段升序的数据的结束标志是它的下一个元素比它小
  3. 所以,根据这个特点,我们可以生成一个数组,这个数组记录了这段数据中,每一处“前一个元素比后一个元素大”的位置
    比如,1 2 3 5 6 5 4 3 5 6 经过分组后,变成:
    [1 2 3 5 6] [5] [4] [3 5 6]
    而这个数组记录了每一组的开头的位置。
  4. 同时,我们知道归并排序的merge()函数需要知道某一段数据的开头、中间、结尾位置,便于合并两段(下面的代码有写)。这个merge()函数与普通的归并排序函数一模一样,我们需要更改的,只是传入的开头、中间、结尾位置
  5. 得到分组后,我们只需要合并前两个组,直到组数为1。这个过程中需要合并的总次数为组数 - 1
    接第3点的例子,
    [1 2 3 5 6] [5] [4] [3 5 6]
    [1 2 3 5 5 6] [4] [3 5 6]
    [1 2 3 4 5 5 6] [3 5 6]
    [1 2 3 3 4 5 5 5 6 6]
    
  6. 代码注释应该算是非常清楚了。

源程序清单

#include<iostream>
constexpr int N = 10;	//数组长度
using namespace std;

//void printArr(int a[]) {
//	for(int i =0; i < N; i++)
//		cout << a[i] << " ";
//	cout << "\n";
//}


//利用pos数组记录长度为a的数组的升序的“断掉的地方”,
//pos记录的是升序的起始位置,如果需要上一个组的结束位置,减一即可
//n是a 的长度
int getPos(int a[], int pos[]) {
	int tem = 1;	//pos要记录的下标从1开始
	pos[0] = 0;	//首先开头记录为0,因为第一个组的起始位置一定是0
	for (int i = 0; i < N - 1; i++) {//注意此处的 n - 1 ,因为是i+1,防止越界!
		if (a[i] > a[i + 1]) {	//举例:1 2 3 5 0 1 2 3 中,从5到0就是个断点,此时5 > 0,记录好0的位置
			pos[tem++] = i + 1;//记录非升序的部分,这样就可以分成多个升序的组,通过pos记录下标
		}
	}
	return tem;	//返回pos的尾标
}

//将数组a的beg到mid和mid+1到end的两组通过数组b合并到一起
void merge(int* a, int* b, int beg, int mid, int end) {
	int i = beg,	//i 是遍历前半部分数组,上限是mid
		j = mid + 1, //j 是第二个数组起始的下标,上限是end
		k = beg;	//k 是遍历数组b,上限是end

	while (i <= mid && j <= end) {	//开始合并,i、j记录比较到这两半数组的哪里了
		if (a[i] < a[j])	//看这两部分中,哪个值更小,如果a[i]更小就把它塞到b[k]里面去。
			b[k++] = a[i++];	//++直接放在[]里,不用单独写,更简洁
		else
			b[k++] = a[j++];	//如果a[j]更小就把它塞到b[k]里面去。
	}

	while (i <= mid) b[k++] = a[i++];	//如果j到顶了,即后半部分全部塞进去了,那么前半部分直接按顺序填入即可
	while (j <= end) b[k++] = a[j++];	//同理

	for (i = beg; i <= end; i++)
		a[i] = b[i];
	//cout << "a= "; printArr(a); cout << "\n"; //debug
	//cout << "b= "; printArr(b); cout << "\n";	//debug
}

//a[]:原始数组,b[]:临时数组
void mergeSort(int a[],int b[]) {
	int pos[N];	//定义pos数组
	int posLen = getPos(a, pos);	//生成pos数组并获取其长度

	//总需的merge次数为:组数(posLen) - 1。
	for (int i = 0; i < posLen - 2; i++) {	//排序
		merge(a, b, pos[0], pos[i + 1] - 1, pos[i + 2] - 1);
		//cout << "a merge = "; printArr(a); cout << "\n"; //debug
	}//这个过程完毕后,还会剩下最后两个组

	merge(a, b, pos[0], pos[posLen - 1] - 1, N - 1);	//合并最后的2组
}


int main() {
	int a[N] = {1, 2, 3, 0, 1, 0 ,5 ,6, 5, 4};	//分成5组
	int b[N];
	mergeSort(a, b);
	for (int i : a) cout << i << " ";
}

第六题

题目

假设要排序的数据最多是4位十进制的正整数,编写对应的基数排序程序。
链表结点结构: struct nofr {int data; nofe *link };

例如数据如下: 30 901 48 9 3912 88 1925
排序后: 9 30 48 88 901 1925 3912

思路

  1. 这是个典型的基数排序
  2. 由于我们是用链表进行操作,所以我们需要一个结点数组来储存每个“桶”的头结点,还需要一个数组来指向每一个“桶”的尾结点。
  3. 对于链表中的每一个结点,我们做如下处理:
    • 首先得到它的倒数第i位数(从个位到高位),如果位不足就视为0。
    • 接着根据这一位上的数把它放入对应的“桶”里,放入的方式和创建链表差不多。
  4. 遍历完所有结点后,我们得到了按某一位排好的数。将这些数从0开始,依次取出,放回到链表里。(这里我采取的是覆盖式的,因为这些桶已经装了整段数据,所以可以直接覆盖)
  5. 重复第3~5点,直到最高位。

源程序清单

  • 此处我们先确定了桶数和最高位数,所以直接定义常量
#include<iostream>
constexpr auto N = 10;	//有10个桶,0~9;
constexpr auto DIG = 4;	//最大位数为4;
using namespace std;

struct node { 
    int data;
    node* link;
};

//作用:获取a的倒数第n + 1位数字
int getNNum(int a, int n) {
    while (n--)
        a = a / N;  //这里的N可以是10,为了使程序统一
    return a % N;
}

void radixSort(node* arr) {
    if (arr == NULL) return;

    node* p = arr;  //p的作用是遍历链表
    for (int i = 0; i < DIG; i++) { //排4次
        node* head[N] = { NULL };  //十个桶的头结点 一定要放在里面,相当于初始化!
        node* tail[N] = { NULL };  //十个桶的尾结点

        p = arr;    //相当于初始化
        while (p != NULL) { //入桶
            int temp = getNNum(p->data, i); //获取本轮次对应的位数的值,记为temp
            if (head[temp] == NULL) {   //如果头结点为空
                head[temp] = new node;
                head[temp]->data = p->data;
                head[temp]->link = NULL;
                tail[temp] = head[temp];
            }
            else {
                node* q = new node;
                q->data = p->data;
                q->link = NULL;
                tail[temp]->link = q;   //连接尾结点
                tail[temp] = q; //更新尾结点
            }
            p = p->link;
        }

        //出桶
        p = arr;
        for (int j = 0; j < N; j++) {   //j就是桶对应的数   
            node* tmp = head[j];    //tmp用于遍历当前桶内的所有元素
            while (tmp != NULL && p != NULL) {
                p->data = tmp->data;    //覆盖式插回
                p = p->link;
                tmp = tmp->link;
            }
        }
    }
}

//创建随机链表
node* createRandLink()
{
    srand((unsigned int)time(NULL));
    node* head = NULL, * p, * q;
    int n = rand() % 15;   //n为有序链表的起点

    if (N == 0) return NULL;

    head = new node;
    p = head;
    p->data = n;
    p->link = NULL;

    for (int i = 1; i < N; i++) {
        q = new node;
        q->data = rand() % 9999;  //随机数
        q->link = NULL;
        p->link = q;
        p = q;
    }
    return head;
}

//这个函数的作用是输出链表
void printLink(node* head) {
    node* p = head;
    while (p != NULL) {
        cout << p->data << " -> ";
        p = p->link;
    }
    cout << "\n";
}

int main() {
    node* arr = createRandLink();
    printLink(arr);

    radixSort(arr);
    printLink(arr);
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值