写单链表的时候突然想到,链表该怎么排序呢? 毕竟我们链表也要有自己的O(nlogn)排序方法!

第一个想法:用quicksort,带头尾指针的双链表确实可以,但是效率不优,因为在选择pivot的时候只能选择最左端,很容易退化成冒泡,更别说单链表了压根就不能用了。

第二个想法:嗯嗯嗯....这种问题肯定前人已经解决过了。所以去看看stl库吧。

用C++ list库,然后写一行 .sort() 再看定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
template<typename _Tp, typename _Alloc>
void
list<_Tp, _Alloc>::
sort()
{
// Do nothing if the list has length 0 or 1.
if (this->_M_impl._M_node._M_next != &this->_M_impl._M_node
&& this->_M_impl._M_node._M_next->_M_next != &this->_M_impl._M_node)
{
list __carry;
list __tmp[64];
list * __fill = __tmp;
list * __counter;
__try
{
do
{
__carry.splice(__carry.begin(), *this, begin());

for(__counter = __tmp;
__counter != __fill && !__counter->empty();
++__counter)
{
__counter->merge(__carry);
__carry.swap(*__counter);
}
__carry.swap(*__counter);
if (__counter == __fill)
++__fill;
}
while ( !empty() );

for (__counter = __tmp + 1; __counter != __fill; ++__counter)
__counter->merge(*(__counter - 1));
swap( *(__fill - 1) );
}
__catch(...)
{
this->splice(this->end(), __carry);
for (int __i = 0; __i < sizeof(__tmp)/sizeof(__tmp[0]); ++__i)
this->splice(this->end(), __tmp[__i]);
__throw_exception_again;
}
}
}

点击并拖拽以移动

这里确实有点看不太懂。

关于stl这里的源码解析,推荐:C++ SGI STL 的 list::sort() 分析_list.sort函数 c++-CSDN博客

解析的很清晰,用的是非递归的mergesort,尤其是运用splice函数和counter数组,充分发挥了链表的优势,如果数组用这种方法反而会变慢。

我这里只是用单链表进行了实践,双向链表也是一样的。

首先定义:

1
2
3
4
typedef struct LIST{
int data;
LIST*next;
}*Linklist;

点击并拖拽以移动

几个辅助函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void init_list(Linklist & list)
{
Linklist p= new LIST;
list=p;
list->next=NULL;
}

//尾插法初始化
void init(Linklist&list)
{
int n,x;
Linklist p=list;

cin>>n;

while(n--)
{
cin>>x;

Linklist temp=new LIST;
temp->next=NULL;
temp->data=x;

p->next=temp;
p=p->next;
}
}

void show(Linklist&list)
{
Linklist p=list;

while(p->next!=NULL)
{
cout<<p->next->data<<' ';
p=p->next;
}

cout<<endl;
return;
}

点击并拖拽以移动

然后是stl库中几个辅助函数的时间:

empty():

1
2
3
4
bool empty(Linklist&p)
{
return p==NULL||p->next==NULL;
}

点击并拖拽以移动

splice(): 因为只需要用到第一个节点,所以我这里只要了首节点。

1
2
3
4
5
6
7
void splice_front(Linklist&head,Linklist & p)
{
Linklist temp=head->next;
head->next=temp->next;
temp->next=p->next;
p->next=temp;
}

点击并拖拽以移动

merge():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void list_merge(Linklist &list,Linklist& p)
{
Linklist it=list,p1=list->next,p2=p->next;

while(p1&&p2)
{
if(p1->data<p2->data)
{
it->next=p1;
p1=p1->next;
}
else
{
it->next=p2;
p2=p2->next;
}

it=it->next;
}

if(!p1)it->next=p2;
else it->next=p1;

p->next=NULL;
}

点击并拖拽以移动

最后是sort部分的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void list_sort(Linklist& list)
{
if(list->next==NULL||list->next->next==NULL)return;

Linklist carry=new LIST;
Linklist counter[64];

for(int i=0;i<64;++i)
counter[i]=new LIST,counter[i]->next=NULL;

carry->next=NULL;

int fill=0;

while(!empty(list))
{
splice_front(list,carry);
int i=0;

while(i<fill&&!empty(counter[i]))
{
list_merge(counter[i],carry);

swap(carry,counter[i++]);
}

swap(carry,counter[i]);

if(i==fill)++fill;
}

for(int i=1;i<fill;++i)
list_merge(counter[i],counter[i-1]);

list->next=counter[fill-1]->next;

for(int i=0;i<64;++i)
delete counter[i];

delete carry;

return;
}

点击并拖拽以移动

全部代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <iostream>
#include <list>

#define ll long long

using namespace std;

typedef struct LIST{
int data;
LIST*next;
}*Linklist;

void init_list(Linklist & list)
{
Linklist p= new LIST;
list=p;
list->next=NULL;
}

//尾插法初始化
void init(Linklist&list)
{
int n,x;
Linklist p=list;

cin>>n;

while(n--)
{
cin>>x;

Linklist temp=new LIST;
temp->next=NULL;
temp->data=x;

p->next=temp;
p=p->next;
}
}

void show(Linklist&list)
{
Linklist p=list;

while(p->next!=NULL)
{
cout<<p->next->data<<' ';
p=p->next;
}

cout<<endl;
return;
}

bool empty(Linklist&p)
{
return p==NULL||p->next==NULL;
}

void splice_front(Linklist&head,Linklist & p)
{
Linklist temp=head->next;
head->next=temp->next;
temp->next=p->next;
p->next=temp;
}

void list_merge(Linklist &list,Linklist& p)
{
Linklist it=list,p1=list->next,p2=p->next;

while(p1&&p2)
{
if(p1->data<p2->data)
{
it->next=p1;
p1=p1->next;
}
else
{
it->next=p2;
p2=p2->next;
}

it=it->next;
}

if(!p1)it->next=p2;
else it->next=p1;

p->next=NULL;
}


void list_sort(Linklist& list)
{
if(list->next==NULL||list->next->next==NULL)return;

Linklist carry=new LIST;
Linklist counter[64];

for(int i=0;i<64;++i)
counter[i]=new LIST,counter[i]->next=NULL;

carry->next=NULL;

int fill=0;

while(!empty(list))
{
splice_front(list,carry);
int i=0;

while(i<fill&&!empty(counter[i]))
{
list_merge(counter[i],carry);

swap(carry,counter[i++]);
}

swap(carry,counter[i]);

if(i==fill)++fill;
}

for(int i=1;i<fill;++i)
list_merge(counter[i],counter[i-1]);

list->next=counter[fill-1]->next;

for(int i=0;i<64;++i)
delete counter[i];

delete carry;

return;
}

int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);

Linklist it;

init_list(it);
init(it);

list_sort(it);

show(it);

cout<<endl;
system("pause");
return 0;
}

点击并拖拽以移动

去洛谷试试吧:P1177 【模板】排序 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

用时104ms

内存3.67MB

内存逼近O(logn)但是list的空间密度较低,所以内存较大,时间和数组的quicksort逼近了。让我怀疑在数组中 nlogn 的排序方法中mergesort之所以是 n 的空间复杂度,就是因为它实际上是为list设计的。