C++ 带链表的稳定快速排序
我正在尝试使用链表实现一个稳定的快速排序,由于某些原因,这段代码正在崩溃。它似乎在崩溃之前进入分区中while循环的第二次迭代 基本上,我使用头部作为轴心点,任何小于轴心点的东西都会进入一个新的链表,任何大于轴心点的东西也会进入一个新的链表。这是递归调用的,然后在最后它们被合并C++ 带链表的稳定快速排序,c++,linked-list,quicksort,C++,Linked List,Quicksort,我正在尝试使用链表实现一个稳定的快速排序,由于某些原因,这段代码正在崩溃。它似乎在崩溃之前进入分区中while循环的第二次迭代 基本上,我使用头部作为轴心点,任何小于轴心点的东西都会进入一个新的链表,任何大于轴心点的东西也会进入一个新的链表。这是递归调用的,然后在最后它们被合并 class Node { public: Node* next; int key; int key2; Node()
class Node
{
public:
Node* next;
int key;
int key2;
Node()
{
next = NULL;
}
};
void fillRandom(Node*& root, int length)
{
if(length != 0){
root->key = rand()%10;
root->next = new Node;
fillRandom(root->next, length-1);
}
}
void partitionLL(Node*& lessThan, Node*& greaterThan, Node*& root)
{
Node* lessThanTemp = lessThan; Node* greaterThanTemp = greaterThan;
Node* temp = root;
while(temp->next != NULL)
{
if(temp->key > root->key){
greaterThanTemp = temp;
greaterThanTemp->next = new Node;
greaterThanTemp = greaterThanTemp->next;
}else{
lessThanTemp = temp;
lessThanTemp->next = new Node;
lessThanTemp = lessThanTemp->next;
}
temp = temp->next;
}
}
void quickSort(Node* root)
{
if(root->next != NULL){//i.e. theres only two nodes in the list
Node* lessThan = new Node; Node* greaterThan = new Node;
partitionLL(lessThan, greaterThan, root);
quickSort(lessThan);
quickSort(greaterThan);
}else{
return;
}
}
int main()
{
int length = 15;
Node* root = new Node;
fillRandom(root, length);
quickSort(root);
}
你的想法成立了(建立两个链表)。这是缺乏的分离和再附着代码。处理链表时,首先要记住的是,在进行重新排列时,很少需要分配新节点。您可以使用给定的节点。这就是他们在那里的目的。您的算法所要做的只是重新排列其中的指针
以下内容正是这样做的。一旦分配了列表,就不需要新节点。除此之外,算法与您的想法相同:
- 使用头部节点作为轴值。将其从列表中分离
- 沿途将列表定位节点的其余部分枚举到两个列表中的一个。注意:从一个列表移动到另一个列表的代码将节点添加到各自列表的末尾
- 完成后,您将拥有两个列表和一个孤独的pivot节点。终止两个列表并递归
- 一旦递归返回seek到左侧列表的末尾。这是固定枢轴节点的位置,然后右侧列表将连接到该位置李>
#include <iostream>
#include <iterator>
#include <algorithm>
#include <random>
struct Node
{
Node* next;
int key;
int key2;
Node( int key, int key2 )
: key(key), key2(key2), next()
{}
};
Node * createList(size_t N)
{
std::random_device rd;
std::mt19937 rng(rd());
std::uniform_int_distribution<> dist(1,10);
Node *root = nullptr;
Node **pp = &root;
for (int i=0; i<N; ++i)
{
*pp = new Node(dist(rng), i+1);
pp = &(*pp)->next;
}
return root;
}
void freeList(Node *& root)
{
while (root)
{
Node *tmp = root;
root = tmp->next;
delete tmp;
}
}
void printList(const Node* root)
{
for (;root;root = root->next)
std::cout << root->key << '(' << root->key2 << ") ";
std::cout << '\n';
}
// quicksort a linked list.
void quickSort(Node *&root)
{
// trivial lists are just returned immediately
if (!root || !(root->next))
return;
Node *lhs = nullptr, **pplhs = &lhs;
Node *rhs = nullptr, **pprhs = &rhs;
Node *pvt = root;
root = root->next;
pvt->next = nullptr;
while (root)
{
if (root->key < pvt->key)
{
*pplhs = root; // tack on lhs list end
pplhs = &(*pplhs)->next;
}
else
{
*pprhs = root; // tack on rhs list end
pprhs = &(*pprhs)->next;
}
root = root->next;
}
// terminate both list. note that the pivot is still
// unlinked, and will remain so until we merge
*pplhs = *pprhs = nullptr;
// invoke on sublists.
quickSort(lhs);
quickSort(rhs);
// find end of lhs list, slip the pivot into position, then
// tack on the rhs list.
while (*pplhs)
pplhs = &(*pplhs)->next;
*pplhs = pvt;
pvt->next = rhs;
// set final output
root = lhs;
}
int main()
{
Node *root = createList(20);
printList(root);
quickSort(root);
printList(root);
freeList(root);
return 0;
}
parens中的数字是原始列表中节点的原始顺序。注意,我特别选择了一个小的随机池进行选择,以体验大量相同的密钥,从而证明排序是稳定的;like键保留其原始列表顺序。例如,原始列表中有五个8
值。排序后,它们的顺序是:
8(5) 8(12) 8(14) 8(17) 8(19)
这是有意的,并且通过确保当我们将项目移动到拆分列表时,我们总是将它们固定在末端来实现
无论如何,我希望它能有所帮助。现在是学习如何使用调试器的最佳时机。如果生成程序的调试版本并在调试器中运行,则调试器将在崩溃处停止。然后,调试器将允许您检查和遍历函数调用堆栈(例如,如果它在库代码中停止),还允许您检查变量的值。调试器还可以用于逐行检查代码,在这种情况下可能更好,因为它将允许您确切地看到代码正在做什么,以及它为变量分配了什么值。特别是用你的
partionLL
函数,因为(对我来说)它似乎是可疑的。最后一点注意:你实际上没有对任何东西进行排序,由于root->next
指针在您从main
函数第一次调用quickSort
时将为NULL
。最后一件事让我相信您展示给我们的程序不是真正崩溃的程序,由于该程序不会执行任何可能崩溃的操作:它调用快速排序
,然后立即返回,程序退出。这真的是崩溃程序的一部分吗?为了让事情看起来更好,我拿出了我给链表赋值的部分。我会做一个编辑把它放回去。我相信这个错误与temp->next在第一次运行后为空有关。哦,我明白了。这帮了不少忙。我现在意识到我忘了合并它们。然而,我特别问我为什么会崩溃,但事实证明,我甚至不需要这个问题的代码,只需要逻辑。不过我对你在这里做的一些事情有点困惑。为什么ppplhs
是双指针?还有,为什么pplhs=&lhs
。最后,如果节点不为NULL,while(root)
是否返回true?无论如何,这确实有帮助。我也学到了一些东西。非常感谢。我使用指向指针的指针来保存将接收下一个节点指针的指针的地址。它使建立转发列表等工作变得更加容易。指向指针的指针类似于常规的单向指针,但它们所持有的是额外的间接层(它们持有指针的地址)。因此,pplhs=&lhs
表示“将lhs
指针的地址放入pplhs
。关于while(root)
C中的布尔值为零或不为零。空指针与零同义,因此while(root)
表示“whileroot
持有非空值“@amarucci13如果你发现答案有帮助,你可以用答案贴左上角的箭头勾选它。如果找到正确答案,也可以选中复选框。现在,在选择这个作为最佳答案之前,我会加快速度,看看是否有更多的答案出现,这可能更合适。是的,我试过了,但没有足够的声誉哦,在这里,你需要15票来提高投票率。@amarucci13啊,糟糕透了。好吧,希望能有帮助。花一些时间学习调试,然后在调试器中逐步完成代码(代码块或Netbeans中的gdb集成,或Windows、VS等)。听起来奇怪,调试工作代码对理解其工作方式非常有帮助。不管怎样,我很高兴这有帮助。
8(5) 8(12) 8(14) 8(17) 8(19)