链表的排序
- 格式:doc
- 大小:39.50 KB
- 文档页数:10
写出以单链表为存储结构的一组数据的简单选择排序算法。
以下是单链表存储结构的简单选择排序算法的实现:
```python
def selection_sort(link_list):
n = len(link_list)
for i in range(n):
min_idx = i
for j in range(i+1, n):
if link_list[j] < link_list[min_idx]:
min_idx = j
link_list[i], link_list[min_idx] = link_list[min_idx], link_list[i]
return link_list
```
算法的基本思路是,遍历待排序的链表,找到未排序部分中的最小值,然后将该值与链表的第一个元素交换位置,然后再遍历剩余的元素,继续按照此方法进行排序,直到整个链表都被排序。
在上面的实现中,我们通过 `min_idx` 变量来保存当前未排序部分中的最小值的索引。
在第一次遍历时,min_idx 等于 i,因为在未排序部分中,第一个元素通常是最小的。
在后续的遍历中,我们不断地寻找未排序部分中的最小值,并将其与链表的第一个元素交换位置,最后返回排好序的链表。
设计两个有序单链表的合并排序算法有序单链表的合并排序,是一种高效的排序算法,可以在较短的时间内对大量数据进行排序。
这种排序算法的核心在于将两个有序的单链表合并成一个有序的单链表,然后再对整个链表进行排序。
合并排序算法的基本原理是分治法。
将需要排序的数组不断地分解成两个子数组,直到每个子数组只包含一个元素为止。
然后再将这些子数组两两合并,直到整个数组被合并成一个有序的数组为止。
这里介绍两个有序单链表的合并排序算法,它们分别是迭代算法和递归算法。
1. 迭代算法迭代算法是一种通用的算法,它的思路是利用循环结构来重复执行一段相同或相似的代码,从而解决一类问题。
对于有序单链表的合并排序,迭代算法的基本思路是将两个有序单链表的元素依次比较,然后将较小的元素加入到新的链表中,直到两个链表中的元素全部被加入到新链表中为止。
以下是迭代算法的具体实现过程:```// 合并两个有序单链表Node* mergeList(Node* head1, Node* head2) { // 新建一个头结点Node* dummy = new Node(-1);// 定义两个指针,分别指向两个链表的头结点 Node* p = head1;Node* q = head2;// 定义一个指针,指向新链表的最后一个节点 Node* curr = dummy;// 循环比较两个链表中的元素while (p != nullptr && q != nullptr) {if (p->val <= q->val) {curr->next = p;p = p->next;} else {curr->next = q;q = q->next;}curr = curr->next;}// 将剩余的元素加入到新链表中curr->next = p != nullptr ? p : q;// 返回新链表的头结点return dummy->next;}// 归并排序Node* mergeSort(Node* head) {if (head == nullptr || head->next == nullptr) {return head;}// 定义两个指针,一个快指针每次走两步,一个慢指针每次走一步 Node* slow = head;Node* fast = head->next;while (fast != nullptr && fast->next != nullptr) {slow = slow->next;fast = fast->next->next;}// 将链表分成两部分Node* head1 = head;Node* head2 = slow->next;slow->next = nullptr;// 分别对两部分链表进行归并排序head1 = mergeSort(head1);head2 = mergeSort(head2);// 合并两个有序单链表return mergeList(head1, head2);}```2. 递归算法递归算法的思想是将一个大问题分解成若干个小问题,然后逐个解决这些小问题,最终得到大问题的解决方案。
链表的排序时间复杂度O(nlogn)思路:⽤归并排序。
对⼀个链表采⽤递归进⾏⼆等分,直到每个部分有序,然后对其进⾏合并。
其实就是两步,先分解,然后合并有序链表。
代码://对链表采⽤递归排序class Solution {public:ListNode* sortList(ListNode* head){if(head==NULL||head->next==NULL)return head;return mergeSort(head);}ListNode* mergeSort(ListNode* head){//递归终⽌条件if(head==NULL||head->next==NULL)return head;ListNode* p=head;ListNode* q=head;ListNode* prep=NULL;while (q!=NULL&&q->next!=NULL){q=q->next->next;prep=p;p=p->next;}prep->next=NULL;//对链表进⾏切分ListNode* lhalf=mergeSort(head);ListNode* rhalf=mergeSort(p);ListNode* res=merge(lhalf,rhalf);return res;}//合并两个有序链表ListNode* merge(ListNode* lh,ListNode* rh){ListNode* tempHead=new ListNode(0);ListNode* p=tempHead;while (lh&&rh){if(lh->val<=rh->val){p->next=lh;lh=lh->next;}else{p->next=rh;rh=rh->next;}p=p->next;}if(lh==NULL)p->next=rh;elsep->next=lh;p=tempHead->next;tempHead->next=NULL;delete tempHead;return p;}};。
单链表上容易实现的排序方法嘿,朋友们!今天咱来聊聊单链表上那些容易实现的排序方法。
你想想啊,单链表就像是一串珠子,每个珠子都有它自己的位置和信息。
那怎么把这些珠子排得整整齐齐呢?咱先说冒泡排序吧!这就好像是在一群小朋友里,让矮个子一个一个地慢慢往前站,把高个子往后挤。
每次都把最大的那个“珠子”给浮到最上面去。
虽然它比较简单直接,但有时候可能会有点慢悠悠的哦!就像你着急出门,却发现钥匙找半天,是不是有点让人着急呀?再来看看插入排序。
这就像是玩扑克牌的时候整理手牌,拿到一张牌,就看看该把它插到哪里合适。
嘿,这多形象呀!它可以一点点地把链表变得有序,虽然可能步骤多了点,但效果还是不错的哟!还有选择排序呢!这就好像是在一群选手中挑出最厉害的那个,然后把其他的依次排好。
是不是挺有意思的?它也能完成排序的任务呢!那咱为啥要用这些排序方法呀?这还用问吗?就像你收拾房间,总不能让东西乱七八糟地堆着吧!把链表排好序,才能更方便我们查找和使用其中的数据呀!不然找个数据都得费半天劲,那多麻烦呀!这些排序方法各有各的特点和用处。
就像不同的工具,有的适合干这个,有的适合干那个。
我们得根据具体情况来选择合适的排序方法,可不能瞎用哦!不然可能会事倍功半呢!比如说,如果链表的数据量不是特别大,那冒泡排序或者插入排序可能就挺合适的。
但要是数据量大得吓人,那可能就得考虑更高效的方法啦!总之呢,单链表上的排序方法就像是我们生活中的各种小技巧,学会了它们,就能让我们的编程之路更加顺畅。
所以呀,大家可得好好掌握这些方法哦!可别小瞧了它们,它们能帮我们解决大问题呢!现在,你是不是对单链表上的排序方法有了更清楚的认识啦?。
链表的反转与合并掌握链表反转和合并操作的实现链表是一种常见的数据结构,它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。
链表的反转和合并是链表操作中常见且重要的操作,在很多编程问题中都有应用。
本文将介绍链表的反转和合并操作的实现方法。
一、链表的反转链表的反转是指将链表中节点的顺序反向排列。
例如,对于链表1→2→3→4→5,反转后的链表为5→4→3→2→1。
实现链表的反转有两种常见的方法:迭代法和递归法。
1. 迭代法迭代法的实现思路是,从链表头节点开始,依次遍历每个节点,将该节点的指针指向前一个节点。
具体步骤如下:1)定义三个指针:当前节点指针cur、前一个节点指针prev、下一个节点指针next。
2)遍历链表,将当前节点的指针指向前一个节点,然后更新prev、cur和next指针的位置。
3)重复上述步骤,直到遍历到链表末尾。
以下是迭代法的实现代码示例(使用Python语言):```pythondef reverse_list(head):prev = Nonecur = headwhile cur:next = cur.nextcur.next = prevprev = curcur = nextreturn prev```2. 递归法递归法的实现思路是,从链表的尾节点开始,依次反转每个节点。
具体步骤如下:1)递归地反转除最后一个节点外的链表。
2)将当前节点的指针指向前一个节点。
3)返回反转后的链表的头节点。
以下是递归法的实现代码示例(使用Python语言):```pythondef reverse_list(head):if not head or not head.next:return headnew_head = reverse_list(head.next)head.next.next = headhead.next = Nonereturn new_head```二、链表的合并链表的合并是指将两个有序链表按照一定的规则合并成一个有序链表。
链表排序(冒泡、选择、插⼊、快排、归并、希尔、堆排序)这篇⽂章分析⼀下链表的各种排序⽅法。
以下排序算法的正确性都可以在LeetCode的这⼀题检测。
本⽂⽤到的链表结构如下(排序算法都是传⼊链表头指针作为参数,返回排序后的头指针)struct ListNode {int val;ListNode *next;ListNode(int x) : val(x), next(NULL) {}};插⼊排序(算法中是直接交换节点,时间复杂度O(n^2),空间复杂度O(1))class Solution {public:ListNode *insertionSortList(ListNode *head) {// IMPORTANT: Please reset any member data you declared, as// the same Solution instance will be reused for each test case.if(head == NULL || head->next == NULL)return head;ListNode *p = head->next, *pstart = new ListNode(0), *pend = head;pstart->next = head; //为了操作⽅便,添加⼀个头结点while(p != NULL){ListNode *tmp = pstart->next, *pre = pstart;while(tmp != p && p->val >= tmp->val) //找到插⼊位置{tmp = tmp->next; pre = pre->next;}if(tmp == p)pend = p;else{pend->next = p->next;p->next = tmp;pre->next = p;}p = pend->next;}head = pstart->next;delete pstart;return head;}};选择排序(算法中只是交换节点的val值,时间复杂度O(n^2),空间复杂度O(1))class Solution {public:ListNode *selectSortList(ListNode *head) {// IMPORTANT: Please reset any member data you declared, as// the same Solution instance will be reused for each test case.//选择排序if(head == NULL || head->next == NULL)return head;ListNode *pstart = new ListNode(0);pstart->next = head; //为了操作⽅便,添加⼀个头结点ListNode*sortedTail = pstart;//指向已排好序的部分的尾部while(sortedTail->next != NULL){ListNode*minNode = sortedTail->next, *p = sortedTail->next->next;//寻找未排序部分的最⼩节点while(p != NULL){if(p->val < minNode->val)minNode = p;p = p->next;}swap(minNode->val, sortedTail->next->val);sortedTail = sortedTail->next;}head = pstart->next;delete pstart;return head;}};快速排序1(算法只交换节点的val值,平均时间复杂度O(nlogn),不考虑递归栈空间的话空间复杂度是O(1))这⾥的partition我们参考(选取第⼀个元素作为枢纽元的版本,因为链表选择最后⼀元素需要遍历⼀遍),具体可以参考这⾥我们还需要注意的⼀点是数组的partition两个参数分别代表数组的起始位置,两边都是闭区间,这样在排序的主函数中:void quicksort(vector<int>&arr, int low, int high){if(low < high){int middle = mypartition(arr, low, high);quicksort(arr, low, middle-1);quicksort(arr, middle+1, high);}}对左边⼦数组排序时,⼦数组右边界是middle-1,如果链表也按这种两边都是闭区间的话,找到分割后枢纽元middle,找到middle-1还得再次遍历数组,因此链表的partition采⽤前闭后开的区间(这样排序主函数也需要前闭后开区间),这样就可以避免上述问题class Solution {public:ListNode *quickSortList(ListNode *head) {// IMPORTANT: Please reset any member data you declared, as// the same Solution instance will be reused for each test case.//链表快速排序if(head == NULL || head->next == NULL)return head;qsortList(head, NULL);return head;}void qsortList(ListNode*head, ListNode*tail){//链表范围是[low, high)if(head != tail && head->next != tail){ListNode* mid = partitionList(head, tail);qsortList(head, mid);qsortList(mid->next, tail);}}ListNode* partitionList(ListNode*low, ListNode*high){//链表范围是[low, high)int key = low->val;ListNode* loc = low;for(ListNode*i = low->next; i != high; i = i->next)if(i->val < key){loc = loc->next;swap(i->val, loc->val);}swap(loc->val, low->val);return loc;}};快速排序2(算法交换链表节点,平均时间复杂度O(nlogn),不考虑递归栈空间的话空间复杂度是O(1))这⾥的partition,我们选取第⼀个节点作为枢纽元,然后把⼩于枢纽的节点放到⼀个链中,把不⼩于枢纽的及节点放到另⼀个链中,最后把两条链以及枢纽连接成⼀条链。
链表逆序的三种方法链表是一种常用的数据结构,由一个个节点通过指针连接而成。
在实际编程中,经常需要对链表进行逆序操作,以满足特定需求。
本文将介绍链表逆序的三种常用方法,分别是迭代法、递归法以及使用栈的方法。
迭代法:迭代法是一种比较直观的逆序方法,通过调整节点之间的指针指向来实现。
具体步骤如下:1. 定义三个指针,分别为当前节点(cur)、前一个节点(prev)和下一个节点(next)。
2. 将当前节点的下一个节点保存到next指针中,以免链表断开。
3. 将当前节点的next指针指向前一个节点,完成逆序操作。
4. 将当前节点赋值给prev指针,以备下一次迭代使用。
5. 将next指针赋值给cur指针,继续下一次迭代。
若next指针为空,则说明已到达链表尾部,逆序完成。
递归法:递归法是一种更为简洁的逆序方法,通过递归调用实现链表逆序。
具体步骤如下:1. 首先判断链表是否为空或只有一个节点,若是则无需逆序,直接返回。
2. 若链表有多个节点,则递归调用逆序函数对除第一个节点外的子链表进行逆序。
3. 将头节点(首节点)的指针指向调用逆序函数后的新链表的尾节点。
4. 将尾节点的指针指向头节点,使得整个链表逆序完成。
使用栈的方法:栈是一种后进先出(LIFO)的数据结构,可以利用栈的特性进行链表逆序操作。
具体步骤如下:1. 遍历链表,将链表中的节点依次压入栈中。
2. 弹出栈中的节点,按照出栈顺序重新构建链表。
弹出的第一个节点是原链表的尾节点,成为逆序链表的头节点。
3. 将每个弹出的节点的next指针指向下一个被弹出的节点,完成逆序操作。
4. 最后一个被弹出的节点成为逆序链表的尾节点,将其next指针置为空,表示逆序链表的尾部。
以上是三种常见的链表逆序方法。
在实际应用中,可以根据具体情况选择合适的方法来实现链表逆序。
迭代法适合逆序链表并保持链表结构的情况;递归法适用于逆序链表不要求保持原结构的情况;使用栈的方法适用于逆序链表并重新构建链表结构的情况。
==========================功能:选择排序(由小到大)返回:指向链表表头的指针==========================*//*选择排序的基本思想就是反复从还未排好序的那些节点中,选出键值(就是用它排序的字段,我们取学号num为键值)最小的节点,依次重新组合成一个链表。
我认为写链表这类程序,关键是理解:head存储的是第一个节点的地址,head->next存储的是第二个节点的地址;任意一个节点p的地址,只能通过它前一个节点的next来求得。
单向链表的选择排序图示:---->[1]---->[3]---->[2]...---->[n]---->[NULL](原链表)head 1->next 3->next 2->next n->next---->[NULL](空链表)firsttail---->[1]---->[2]---->[3]...---->[n]---->[NULL](排序后链表)first 1->next 2->next 3->next tail->next图10:有N个节点的链表选择排序1、先在原链表中找最小的,找到一个后就把它放到另一个空的链表中;2、空链表中安放第一个进来的节点,产生一个有序链表,并且让它在原链表中分离出来(此时要注意原链表中出来的是第一个节点还是中间其它节点);3、继续在原链表中找下一个最小的,找到后把它放入有序链表的尾指针的next,然后它变成其尾指针;*/struct student *SelectSort(struct student *head){struct student *first; /*排列后有序链的表头指针*/struct student *tail; /*排列后有序链的表尾指针*/struct student *p_min; /*保留键值更小的节点的前驱节点的指针*/struct student *min; /*存储最小节点*/struct student *p; /*当前比较的节点*/first = NULL;while (head != NULL) /*在链表中找键值最小的节点。
*/{/*注意:这里for语句就是体现选择排序思想的地方*/for (p=head,min=head; p->next!=NULL; p=p->next) /*循环遍历链表中的节点,找出此时最小的节点。
*/{if (p->next->num < min->num) /*找到一个比当前min小的节点。
*/{p_min = p; /*保存找到节点的前驱节点:显然p->next的前驱节点是p。
*/min = p->next; /*保存键值更小的节点。
*/}}/*上面for语句结束后,就要做两件事;一是把它放入有序链表中;二是根据相应的条件判断,安排它离开原来的链表。
*//*第一件事*/if (first == NULL) /*如果有序链表目前还是一个空链表*/{first = min; /*第一次找到键值最小的节点。
*/tail = min; /*注意:尾指针让它指向最后的一个节点。
*/}else /*有序链表中已经有节点*/{tail->next = min; /*把刚找到的最小节点放到最后,即让尾指针的next指向它。
*/tail = min; /*尾指针也要指向它。
*/}/*第二件事*/if (min == head) /*如果找到的最小节点就是第一个节点*/{head = head->next; /*显然让head指向原head->next,即第二个节点,就OK*/}else /*如果不是第一个节点*/{p_min->next = min->next; /*前次最小节点的next指向当前min的next,这样就让min离开了原链表。
*/}}if (first != NULL) /*循环结束得到有序链表first*/{tail->next = NULL; /*单向链表的最后一个节点的next应该指向NULL*/}head = first;return head;}/*==========================功能:直接插入排序(由小到大)返回:指向链表表头的指针==========================*//*直接插入排序的基本思想就是假设链表的前面n-1个节点是已经按键值(就是用它排序的字段,我们取学号num为键值)排好序的,对于节点n在这个序列中找插入位置,使得n插入后新序列仍然有序。
按照这种思想,依次对链表从头到尾执行一遍,就可以使无序链表变为有序链表。
单向链表的直接插入排序图示:---->[1]---->[3]---->[2]...---->[n]---->[NULL](原链表)head 1->next 3->next 2->next n->next---->[1]---->[NULL](从原链表中取第1个节点作为只有一个节点的有序链表)head图11---->[3]---->[2]...---->[n]---->[NULL](原链表剩下用于直接插入排序的节点)first 3->next 2->next n->next图12---->[1]---->[2]---->[3]...---->[n]---->[NULL](排序后链表)head 1->next 2->next 3->next n->next图13:有N个节点的链表直接插入排序1、先在原链表中以第一个节点为一个有序链表,其余节点为待定节点。
2、从图12链表中取节点,到图11链表中定位插入。
3、上面图示虽说画了两条链表,其实只有一条链表。
在排序中,实质只增加了一个用于指向剩下需要排序节点的头指针first罢了。
这一点请读者务必搞清楚,要不然就可能认为它和上面的选择排序法一样了。
*/struct student *InsertSort(struct student *head){struct student *first; /*为原链表剩下用于直接插入排序的节点头指针*/struct student *t; /*临时指针变量:插入节点*/struct student *p; /*临时指针变量*/struct student *q; /*临时指针变量*/first = head->next; /*原链表剩下用于直接插入排序的节点链表:可根据图12来理解。
*/ head->next = NULL; /*只含有一个节点的链表的有序链表:可根据图11来理解。
*/while (first != NULL) /*遍历剩下无序的链表*/{/*注意:这里for语句就是体现直接插入排序思想的地方*/for (t=first, q=head; ((q!=NULL) && (q->num < t->num)); p=q, q=q->next); /*无序节点在有序链表中找插入的位置*//*退出for循环,就是找到了插入的位置*//*注意:按道理来说,这句话可以放到下面注释了的那个位置也应该对的,但是就是不能。
原因:你若理解了上面的第3条,就知道了。
*/first = first->next; /*无序链表中的节点离开,以便它插入到有序链表中。
*/if (q == head) /*插在第一个节点之前*/{head = t;}else /*p是q的前驱*/{p->next = t;}t->next = q; /*完成插入动作*//*first = first->next;*/}return head;}/*==========================功能:冒泡排序(由小到大)返回:指向链表表头的指针==========================*//*直接插入排序的基本思想就是对当前还未排好序的范围内的全部节点,自上而下对相邻的两个节点依次进行比较和调整,让键值(就是用它排序的字段,我们取学号num为键值)较大的节点往下沉,键值较小的往上冒。
即:每当两相邻的节点比较后发现它们的排序与排序要求相反时,就将它们互换。
单向链表的冒泡排序图示:---->[1]---->[3]---->[2]...---->[n]---->[NULL](原链表)head 1->next 3->next 2->next n->next---->[1]---->[2]---->[3]...---->[n]---->[NULL](排序后链表)head 1->next 2->next 3->next n->next图14:有N个节点的链表冒泡排序任意两个相邻节点p、q位置互换图示:假设p1->next指向p,那么显然p1->next->next就指向q,p1->next->next->next就指向q的后继节点,我们用p2保存p1->next->next指针。
即:p2=p1->next->next,则有:[ ]---->[p]---------->[q]---->[ ](排序前)p1->next p1->next->next p2->next图15[ ]---->[q]---------->[p]---->[ ](排序后)图161、排序后q节点指向p节点,在调整指向之前,我们要保存原p的指向节点地址,即:p2=p1->next->next;2、顺着这一步一步往下推,排序后图16中p1->next->next要指的是p2->next,所以p1->next->next=p2->next;3、在图15中p2->next原是q发出来的指向,排序后图16中q的指向要变为指向p的,而原来p1->next是指向p的,所以p2->next=p1->next;4、在图15中p1->next原是指向p的,排序后图16中p1->next要指向q,原来p1->next->next (即p2)是指向q的,所以p1->next=p2;5、至此,我们完成了相邻两节点的顺序交换。