C++ C++;合并链表排序的实现在连接多个节点的子列表时失败
我一直在致力于链表的模板化实现,目的是重新发明轮子,偶然发现这种类型的问题,以帮助学习指向类实例处理的指针的细微差别。我遇到的问题与合并子列表有关,在第二次合并(第一次合并,子列表可以有多个节点)失败时,先前的类实例(来自C++ C++;合并链表排序的实现在连接多个节点的子列表时失败,c++,templates,mergesort,C++,Templates,Mergesort,我一直在致力于链表的模板化实现,目的是重新发明轮子,偶然发现这种类型的问题,以帮助学习指向类实例处理的指针的细微差别。我遇到的问题与合并子列表有关,在第二次合并(第一次合并,子列表可以有多个节点)失败时,先前的类实例(来自split或mergesorted)似乎超出范围(这不应该对合并有任何影响,因为指针分配是一个始终保留在范围内的前一个列表,直到原始列表节点的分配发生之后) 这里的关键是,所有类实例都有指向原始列表中原始节点的指针,因此只要子列表实例仍在作用域中,直到子列表的开始节点在上一个递
split
或mergesorted
)似乎超出范围(这不应该对合并有任何影响,因为指针分配是一个始终保留在范围内的前一个列表,直到原始列表节点的分配发生之后)
这里的关键是,所有类实例都有指向原始列表中原始节点的指针,因此只要子列表实例仍在作用域中,直到子列表的开始节点在上一个递归中返回并分配给列表。我正在尝试移动一个完美的100%工作的C实现。因此,这是一个问题我理解为什么我不能像对待C中的结构一样对待类实例,这就是这里的问题——但我不能把我的手指放在解释原因的文档上
类list\u t
包含构成列表的结构节点
/* linked list node */
template <class T>
struct node_t {
T data;
node_t<T> *next;
};
template <class T>
class list_t {
node_t<T> *head, *tail;
int (*cmp)(const node_t<T>*, const node_t<T>*);
public:
list_t (void); /* constructors */
list_t (int(*f)(const node_t<T>*, const node_t<T>*));
~list_t (void); /* destructor */
list_t (const list_t&); /* copy constructor */
/* setter for compare function */
,,,
list_t split (void); /* split list ~ 1/2 */
...
/* merge lists after mergesort_start */
node_t<T> *mergesorted (node_t<T> *a, node_t<T> *b);
void mergesort_run (list_t<T> *l); /* mergesort function */
void mergesort (void); /* wrapper for mergesort */
};
对于mergesort,我有一个包装器,它调用实际的mergesort函数mergesort\u run
/* wrapper to the actual mergesort routing in mergesort_run */
template <class T>
void list_t<T>::mergesort(void)
{
mergesort_run (this);
/* set tail pointer to last node after sort */
for (node_t<T> *pn = head; pn; pn = pn->next)
tail = pn;
}
合并函数,mergesorted
按排序顺序合并子列表:
template <class T>
node_t<T> *list_t<T>::mergesorted (node_t<T> *a, node_t<T> *b)
{
node_t<T> *result = nullptr;
/* Base cases */
if (!a)
return (b);
else if (!b)
return (a);
/* Pick either a or b, and recur */
if (cmp (a, b) <= 0) {
result = a;
result->next = mergesorted (a->next, b);
}
else {
result = b;
result->next = mergesorted (a, b->next);
}
return result;
}
将左子列表拆分为节点12
和11
后,左子列表的开始合并将正常进行。一旦我将11、12
子列表与10
合并,则11、12
子列表的值将消失
MCVE
#include <iostream>
/* linked list node */
template <class T>
struct node_t {
T data;
node_t<T> *next;
};
/* default compare function for types w/overload (ascending) */
template <typename T>
int compare_asc (const node_t<T> *a, const node_t<T> *b)
{
return (a->data > b->data) - (a->data < b->data);
}
/* compare function for types w/overload (descending) */
template <typename T>
int compare_desc (const node_t<T> *a, const node_t<T> *b)
{
return (a->data < b->data) - (a->data > b->data);
}
template <class T>
class list_t {
node_t<T> *head, *tail;
int (*cmp)(const node_t<T>*, const node_t<T>*);
public:
list_t (void); /* constructors */
list_t (int(*f)(const node_t<T>*, const node_t<T>*));
~list_t (void); /* destructor */
list_t (const list_t&); /* copy constructor */
/* setter for compare function */
void setcmp (int (*f)(const node_t<T>*, const node_t<T>*));
node_t<T> *addnode (T data); /* simple add at end */
node_t<T> *addinorder (T data); /* add in order */
void delnode (T data); /* delete node */
void prnlist (void); /* print space separated */
list_t split (void); /* split list ~ 1/2 */
void insertionsort (void); /* insertion sort list */
/* merge lists after mergesort_start */
node_t<T> *mergesorted (node_t<T> *a, node_t<T> *b);
void mergesort_run (list_t<T> *l); /* mergesort function */
void mergesort (void); /* wrapper for mergesort */
};
/* constructor (default) */
template <class T>
list_t<T>::list_t (void)
{
head = tail = nullptr;
cmp = compare_asc;
}
/* constructor taking compare function as argument */
template <class T>
list_t<T>::list_t (int(*f)(const node_t<T>*, const node_t<T>*))
{
head = tail = nullptr;
cmp = f;
}
/* destructor free all list memory */
template <class T>
list_t<T>::~list_t (void)
{
node_t<T> *pn = head;
while (pn) {
node_t<T> *victim = pn;
pn = pn->next;
delete victim;
}
}
/* copy ctor - copy exising list */
template <class T>
list_t<T>::list_t (const list_t& l)
{
cmp = l.cmp; /* assign compare function ptr */
head = tail = nullptr; /* initialize head/tail */
/* copy data to new list */
for (node_t<T> *pn = l.head; pn; pn = pn->next)
this->addnode (pn->data);
}
/* setter compare function */
template <class T>
void list_t<T>::setcmp (int(*f)(const node_t<T>*, const node_t<T>*))
{
cmp = f;
}
/* add using tail ptr */
template <class T>
node_t<T> *list_t<T>::addnode (T data)
{
node_t<T> *node = new node_t<T>; /* allocate/initialize node */
node->data = data;
node->next = nullptr;
if (!head)
head = tail = node;
else {
tail->next = node;
tail = node;
}
return node;
}
template <class T>
node_t<T> *list_t<T>::addinorder (T data)
{
if (!cmp) { /* validate compare function not nullptr */
std::cerr << "error: compare is nullptr.\n";
return nullptr;
}
node_t<T> *node = new node_t<T>; /* allocate/initialize node */
node->data = data;
node->next = nullptr;
node_t<T> **ppn = &head, /* ptr-to-ptr to head */
*pn = head; /* ptr to head */
while (pn && cmp (node, pn) > 0) { /* node sorts after current */
ppn = &pn->next; /* ppn to address of next */
pn = pn->next; /* advance pointer to next */
}
node->next = pn; /* set node->next to next */
if (pn == nullptr)
tail = node;
*ppn = node; /* set current to node */
return node; /* return node */
}
template <class T>
void list_t<T>::delnode (T data)
{
node_t<T> **ppn = &head; /* pointer to pointer to node */
node_t<T> *pn = head; /* pointer to node */
for (; pn; ppn = &pn->next, pn = pn->next) {
if (pn->data == data) {
*ppn = pn->next; /* set address to next */
delete pn;
break;
}
}
}
template <class T>
void list_t<T>::prnlist (void)
{
if (!head) {
std::cout << "empty-list\n";
return;
}
for (node_t<T> *pn = head; pn; pn = pn->next)
std::cout << " " << pn->data;
std::cout << '\n';
}
/* split list l into lists a & b */
template <class T>
list_t<T> list_t<T>::split (void)
{
list_t<T> s; /* new instance of class */
node_t<T> *pa = head, /* pointer to current head */
*pb = pa->next; /* 2nd pointer to double-advance */
while (pb) { /* while not end of list */
pb = pb->next; /* advance 2nd ptr */
if (pb) { /* if not nullptr */
pa = pa->next; /* advance current ptr */
pb = pb->next; /* advance 2nd ptr again */
}
}
s.tail = tail; /* 2nd half tail will be current tail */
tail = pa; /* current tail is at pa */
s.head = pa->next; /* 2nd half head is next ptr */
pa->next = nullptr; /* set next ptr NULL to end 1st 1/2 */
return s; /* return new instance */
}
/** insertion sort of linked list.
* re-orders list in sorted order.
*/
template <class T>
void list_t<T>::insertionsort (void)
{
node_t<T> *sorted = head, /* initialize sorted list to 1st node */
*pn = head->next; /* advance original list node to next */
sorted->next = NULL; /* initialize sorted->next to NULL */
while (pn) { /* iterate over existing from 2nd node */
node_t<T> **pps = &sorted, /* ptr-to-ptr to sorted list */
*ps = *pps, /* ptr to sorted list */
*next = pn->next; /* save list next as separate pointer */
while (ps && cmp(ps, pn) < 0) { /* loop until sorted */
pps = &ps->next; /* get address of next node */
ps = ps->next; /* get next node pointer */
}
*pps = pn; /* insert existing in sort order as current */
pn->next = ps; /* set next as sorted next */
pn = next; /* reinitialize existing pointer to next */
}
head = sorted; /* update head to sorted head */
/* set tail pointer to last node after sort */
for (pn = head; pn; pn = pn->next)
tail = pn;
}
/* FIXME mergesort recursion not working */
template <class T>
node_t<T> *list_t<T>::mergesorted (node_t<T> *a, node_t<T> *b)
{
node_t<T> *result = nullptr;
/* Base cases */
if (!a)
return (b);
else if (!b)
return (a);
/* Pick either a or b, and recur */
if (cmp (a, b) <= 0) {
result = a;
result->next = mergesorted (a->next, b);
}
else {
result = b;
result->next = mergesorted (a, b->next);
}
return result;
}
/* split and merge splits in sort order */
template <class T>
void list_t<T>::mergesort_run (list_t<T> *l)
{
/* Base case -- length 0 or 1 */
if (!l->head || !l->head->next) {
return;
}
/* Split head into 'a' and 'b' sublists */
list_t<T> la = l->split();
/* Recursively sort the sublists */
mergesort_run(l);
mergesort_run(&la);
/* merge the two sorted lists together */
l->head = mergesorted (l->head, la.head);
}
/* wrapper to the actual mergesort routing in mergesort_run */
template <class T>
void list_t<T>::mergesort(void)
{
mergesort_run (this);
/* set tail pointer to last node after sort */
for (node_t<T> *pn = head; pn; pn = pn->next)
tail = pn;
}
int main (void) {
list_t<int> l;
int arr[] = {12, 11, 10, 7, 4, 14, 8, 16, 20, 19,
2, 9, 1, 13, 17, 6, 15, 5, 3, 18};
unsigned asz = sizeof arr / sizeof *arr;
for (unsigned i = 0; i < asz; i++)
l.addnode (arr[i]);
l.prnlist();
#ifdef ISORT
l.insertionsort();
#else
l.mergesort();
#endif
l.prnlist();
}
合并排序的结果--不好
$ ./bin/ll_merge_post
12 11 10 7 4 14 8 16 20 19 2 9 1 13 17 6 15 5 3 18
0 16108560 16108656 16108688 16108560 16108816 16108784 16108848 16108752 16108720 16109072 16108976 16108944 16109008 16108880 16108912 16109136 16109104 16109168 16109040
所以我被卡住了。(这可能是我应该看到但不应该看到的简单的事情)为何子列表的合并失败?在C++结构中处理类实例的关键部分是什么?< p>在<代码> GiuSoRtTyRun<代码>中,您有一个本地列表<代码> La<代码>,包含了您的源列表的一半。在函数的结尾,您将代码< La>代码>的内容合并回新列表,但变量本身仍指向合并的节点。当运行la
的析构函数时,这些节点将被删除
如果在执行合并后将la
的头节点设置为空指针(la.head=nullptr
),则当析构函数运行时,没有任何节点可供其删除
一个不相关的问题是,在创建新列表(如split
)时,您没有将cmp
复制到适当的位置。FWIW,您的MCVE在使用Visual Studio 2019运行时会崩溃,指针0xdddddd无效(这意味着您正在访问解除分配的内存)。如果您有Windows计算机,也许您应该获取社区版并运行代码。谢谢Paul。我没有在Windows上运行它。(我必须重新启动当前计算机才能进入W10 w/VS 2017)。这里发生了一些有趣的事情。这与valgrind
输出相对应,但没有解释代码的gcc/gdb处理方式。我有一个较旧的gcc(Leap上的4.8.5
,但我必须在Arch上尝试,看看它在那里是否得到不同的处理(使用-std=c++11
编译)另外,您没有为list类实现赋值运算符。我不知道这是否有什么不同,但您正在一个函数中按值返回list\u t
(split
),如果没有实现所有“3的规则”,这可能会产生副作用函数。是的,这是我研究的一个领域。回想一下split
从完整列表中提取原始节点,并简单地将列表一分为二(就像C实现那样)并按值返回一个结构/类,其中包含指向原始节点的指针。如果这是由于节点
上的规则3而导致逻辑失败的地方,那么这可能有一定意义,但我之前测试了连接拆分。让我运行另一个快速测试。是的,我建议您启动Visual Studio--错误每次都会重复出现。应该只需要运行几次就可以看出是什么导致了它。是的,但是由于la
持有的节点是原始节点,在调用析构函数之前对联接列表的赋值应该是正确的??对吗?这也对应于C代码中使用的本地list\u t la;
,其行为与预期一致。这就是rEAL让我抓挠我。一个本地C结构很好,但是我找不到任何地方用C++实例处理的差别。.davidC.RANKIN分配是很好的,在节点被复制的意义上,但是原始<代码> La<代码>数据不变(所以它和新列表都指向同一个节点)。当代码> La<代码>被破坏时,合并的节点将被删除,而一半的结果是无效的。C代码很好,因为它没有析构函数。因为 Listyt有一个析构函数,C++代码做的C代码不。我现在得到了,该死的本地实例指向相同的节点,但是析构函数运行。s并清除它们——这就是在这种情况下C/C++结构/类处理之间的区别!!(宾果)现在我该如何禁用析构函数?(我通常会将T->head设置为nullptr
以停止它的运行——这应该在这里起作用)--等等!哇哇!!la.head=nullptr;
是运行mergesort\u
结束时所需的全部内容!(添加此项,我将接受)
/* split list l into lists a & b */
void split (list_t *l, list_t *a)
{
node_t *pa = l->head,
*pb = pa->next;
while (pb) {
pb = pb->next;
if (pb) {
pa = pa->next;
pb = pb->next;
}
}
a->tail = l->tail;
l->tail = pa;
a->head = pa->next;
pa->next = NULL;
}
/* merge splits in sort order */
node_t *mergesorted (node_t *a, node_t *b)
{
node_t *res = NULL;
/* base cases */
if (!a)
return (b);
else if (!b)
return (a);
/* Pick either a or b, and recurse */
if (a->data <= b->data) {
res = a;
res->next = mergesorted (a->next, b);
}
else {
res = b;
res->next = mergesorted (a, b->next);
}
return res;
}
/* sorts the linked list by changing next pointers (not data) */
void mergesort (list_t *l)
{
list_t la;
node_t *head = l->head;
/* Base case -- length 0 or 1 */
if (!head || !head->next) {
return;
}
/* Split head into 'a' and 'b' sublists */
split (l, &la);
/* Recursively sort the sublists */
mergesort(l);
mergesort(&la);
/* answer = merge the two sorted lists together */
l->head = mergesorted (l->head, la.head);
/* set tail pointer to last node after sort */
for (head = l->head; head; head = head->next)
l->tail = head;
}
list_t<int> l;
int arr[] = {12, 11, 10, 7, 4, 14, 8, 16, 20, 19,
2, 9, 1, 13, 17, 6, 15, 5, 3, 18};
unsigned asz = sizeof arr / sizeof *arr;
for (unsigned i = 0; i < asz; i++)
l.addnode (arr[i]);
l.prnlist();
#ifdef ISORT
l.insertionsort();
#else
l.mergesort();
#endif
l.prnlist();
}
#include <iostream>
/* linked list node */
template <class T>
struct node_t {
T data;
node_t<T> *next;
};
/* default compare function for types w/overload (ascending) */
template <typename T>
int compare_asc (const node_t<T> *a, const node_t<T> *b)
{
return (a->data > b->data) - (a->data < b->data);
}
/* compare function for types w/overload (descending) */
template <typename T>
int compare_desc (const node_t<T> *a, const node_t<T> *b)
{
return (a->data < b->data) - (a->data > b->data);
}
template <class T>
class list_t {
node_t<T> *head, *tail;
int (*cmp)(const node_t<T>*, const node_t<T>*);
public:
list_t (void); /* constructors */
list_t (int(*f)(const node_t<T>*, const node_t<T>*));
~list_t (void); /* destructor */
list_t (const list_t&); /* copy constructor */
/* setter for compare function */
void setcmp (int (*f)(const node_t<T>*, const node_t<T>*));
node_t<T> *addnode (T data); /* simple add at end */
node_t<T> *addinorder (T data); /* add in order */
void delnode (T data); /* delete node */
void prnlist (void); /* print space separated */
list_t split (void); /* split list ~ 1/2 */
void insertionsort (void); /* insertion sort list */
/* merge lists after mergesort_start */
node_t<T> *mergesorted (node_t<T> *a, node_t<T> *b);
void mergesort_run (list_t<T> *l); /* mergesort function */
void mergesort (void); /* wrapper for mergesort */
};
/* constructor (default) */
template <class T>
list_t<T>::list_t (void)
{
head = tail = nullptr;
cmp = compare_asc;
}
/* constructor taking compare function as argument */
template <class T>
list_t<T>::list_t (int(*f)(const node_t<T>*, const node_t<T>*))
{
head = tail = nullptr;
cmp = f;
}
/* destructor free all list memory */
template <class T>
list_t<T>::~list_t (void)
{
node_t<T> *pn = head;
while (pn) {
node_t<T> *victim = pn;
pn = pn->next;
delete victim;
}
}
/* copy ctor - copy exising list */
template <class T>
list_t<T>::list_t (const list_t& l)
{
cmp = l.cmp; /* assign compare function ptr */
head = tail = nullptr; /* initialize head/tail */
/* copy data to new list */
for (node_t<T> *pn = l.head; pn; pn = pn->next)
this->addnode (pn->data);
}
/* setter compare function */
template <class T>
void list_t<T>::setcmp (int(*f)(const node_t<T>*, const node_t<T>*))
{
cmp = f;
}
/* add using tail ptr */
template <class T>
node_t<T> *list_t<T>::addnode (T data)
{
node_t<T> *node = new node_t<T>; /* allocate/initialize node */
node->data = data;
node->next = nullptr;
if (!head)
head = tail = node;
else {
tail->next = node;
tail = node;
}
return node;
}
template <class T>
node_t<T> *list_t<T>::addinorder (T data)
{
if (!cmp) { /* validate compare function not nullptr */
std::cerr << "error: compare is nullptr.\n";
return nullptr;
}
node_t<T> *node = new node_t<T>; /* allocate/initialize node */
node->data = data;
node->next = nullptr;
node_t<T> **ppn = &head, /* ptr-to-ptr to head */
*pn = head; /* ptr to head */
while (pn && cmp (node, pn) > 0) { /* node sorts after current */
ppn = &pn->next; /* ppn to address of next */
pn = pn->next; /* advance pointer to next */
}
node->next = pn; /* set node->next to next */
if (pn == nullptr)
tail = node;
*ppn = node; /* set current to node */
return node; /* return node */
}
template <class T>
void list_t<T>::delnode (T data)
{
node_t<T> **ppn = &head; /* pointer to pointer to node */
node_t<T> *pn = head; /* pointer to node */
for (; pn; ppn = &pn->next, pn = pn->next) {
if (pn->data == data) {
*ppn = pn->next; /* set address to next */
delete pn;
break;
}
}
}
template <class T>
void list_t<T>::prnlist (void)
{
if (!head) {
std::cout << "empty-list\n";
return;
}
for (node_t<T> *pn = head; pn; pn = pn->next)
std::cout << " " << pn->data;
std::cout << '\n';
}
/* split list l into lists a & b */
template <class T>
list_t<T> list_t<T>::split (void)
{
list_t<T> s; /* new instance of class */
node_t<T> *pa = head, /* pointer to current head */
*pb = pa->next; /* 2nd pointer to double-advance */
while (pb) { /* while not end of list */
pb = pb->next; /* advance 2nd ptr */
if (pb) { /* if not nullptr */
pa = pa->next; /* advance current ptr */
pb = pb->next; /* advance 2nd ptr again */
}
}
s.tail = tail; /* 2nd half tail will be current tail */
tail = pa; /* current tail is at pa */
s.head = pa->next; /* 2nd half head is next ptr */
pa->next = nullptr; /* set next ptr NULL to end 1st 1/2 */
return s; /* return new instance */
}
/** insertion sort of linked list.
* re-orders list in sorted order.
*/
template <class T>
void list_t<T>::insertionsort (void)
{
node_t<T> *sorted = head, /* initialize sorted list to 1st node */
*pn = head->next; /* advance original list node to next */
sorted->next = NULL; /* initialize sorted->next to NULL */
while (pn) { /* iterate over existing from 2nd node */
node_t<T> **pps = &sorted, /* ptr-to-ptr to sorted list */
*ps = *pps, /* ptr to sorted list */
*next = pn->next; /* save list next as separate pointer */
while (ps && cmp(ps, pn) < 0) { /* loop until sorted */
pps = &ps->next; /* get address of next node */
ps = ps->next; /* get next node pointer */
}
*pps = pn; /* insert existing in sort order as current */
pn->next = ps; /* set next as sorted next */
pn = next; /* reinitialize existing pointer to next */
}
head = sorted; /* update head to sorted head */
/* set tail pointer to last node after sort */
for (pn = head; pn; pn = pn->next)
tail = pn;
}
/* FIXME mergesort recursion not working */
template <class T>
node_t<T> *list_t<T>::mergesorted (node_t<T> *a, node_t<T> *b)
{
node_t<T> *result = nullptr;
/* Base cases */
if (!a)
return (b);
else if (!b)
return (a);
/* Pick either a or b, and recur */
if (cmp (a, b) <= 0) {
result = a;
result->next = mergesorted (a->next, b);
}
else {
result = b;
result->next = mergesorted (a, b->next);
}
return result;
}
/* split and merge splits in sort order */
template <class T>
void list_t<T>::mergesort_run (list_t<T> *l)
{
/* Base case -- length 0 or 1 */
if (!l->head || !l->head->next) {
return;
}
/* Split head into 'a' and 'b' sublists */
list_t<T> la = l->split();
/* Recursively sort the sublists */
mergesort_run(l);
mergesort_run(&la);
/* merge the two sorted lists together */
l->head = mergesorted (l->head, la.head);
}
/* wrapper to the actual mergesort routing in mergesort_run */
template <class T>
void list_t<T>::mergesort(void)
{
mergesort_run (this);
/* set tail pointer to last node after sort */
for (node_t<T> *pn = head; pn; pn = pn->next)
tail = pn;
}
int main (void) {
list_t<int> l;
int arr[] = {12, 11, 10, 7, 4, 14, 8, 16, 20, 19,
2, 9, 1, 13, 17, 6, 15, 5, 3, 18};
unsigned asz = sizeof arr / sizeof *arr;
for (unsigned i = 0; i < asz; i++)
l.addnode (arr[i]);
l.prnlist();
#ifdef ISORT
l.insertionsort();
#else
l.mergesort();
#endif
l.prnlist();
}
$ ./bin/ll_merge_post
12 11 10 7 4 14 8 16 20 19 2 9 1 13 17 6 15 5 3 18
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
$ ./bin/ll_merge_post
12 11 10 7 4 14 8 16 20 19 2 9 1 13 17 6 15 5 3 18
0 16108560 16108656 16108688 16108560 16108816 16108784 16108848 16108752 16108720 16109072 16108976 16108944 16109008 16108880 16108912 16109136 16109104 16109168 16109040