gpt4 book ai didi

c++ - 连接超过1个节点的子列表时,链表合并合并的C++实现失败

转载 作者:行者123 更新时间:2023-12-02 10:23:35 25 4
gpt4 key购买 nike

我一直在研究链表的模板化实现,目的是重新发明轮子,偶然发现这种类型的问题,以帮助学习指向类实例处理的指针的细微差别。我遇到的问题与合并子列表有关,在第二次合并(子列表可以具有多个节点的第一次合并)中,如果先前的类实例(来自splitmergesorted)似乎超出范围,则合并子列表将失败不会对合并产生任何影响,因为指针分配是一个始终存在于作用域中的先前列表,直到原始列表节点的分配发生后为止)

这里的关键是所有类实例都有指向原始列表中原始节点的指针,只要子列表实例保留在作用域中,直到返回子列表的开始节点并在上一个递归中将其分配给列表。我试图移动一个完美的100%工作C实现。因此,我的理解是为什么我不能像在C语言中那样处理类实例,这就是问题所在,但是我不能专心解释为什么的文档。
list_t类包含构成列表的node_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 */
};

(是的,我不知道 _t后缀,这不是重点)
split函数运行正常,并且:
/* 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 */
}

对于mergesort,我有一个包装器,调用了实际的mergesort函数 mergesort_run。这样做是为了仅在排序完成后才调用 tail指针更新,例如
/* 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;
}
mergesort_run如下:
/* 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);
}

合并功能 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;
}

我正在从迁移的正在执行的C实现

上面的每一个(除了我拆分出初始包装器之外)都是以下工作的C拆分/合并排序的实现:
/* 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;
}

在第2次合并中合并第1次合并中的节点消失

我已经使用 gdbvalgrind逐步完成了C++实现。在 gdb中,代码将完整无误地完成,但是在 valgrind中,在释放了一个块之后,您有4字节和8字节的无效读取,这表明析构函数正在释放内存(应该释放),但是在递归展开时完成指针分配依赖于嵌套递归调用中指针的地址,而不是仅仅使用原始地址中的指针值(如上述C代码完美地实现)

发生的事情是,在将列表拆分为具有单个节点的子列表并进行第一次合并之后,我们仍然很好。当下一次展开发生时,您将合并的节点与另一个子列表合并-2节点子列表的值将丢失。因此,在选择了C和C++实现之后,我感觉像个白痴,因为我可以简单地在C中调试/纠正问题,所以我缺少一些批判性的理解,这些理解使我可以对相同代码的C++类实现进行相同的操作。

测试代码

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();
}

在将左子列表拆分为节点 1211之后,左子列表的开始合并就可以了。一旦我将 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();
}

插入排序的结果-预期结果

使用 -DISORT进行编译以测试插入排序(有效):
$ ./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

所以我被卡住了。 (这应该是我应该看到的简单的事情,但不是),为什么子列表合并失败?对C++的类实例理解的关键理解是什么?

最佳答案

mergesort_run中,您有一个本地列表la,其中包含源列表的一半。在函数末尾,您将la的内容合并回新列表中,但是变量本身仍指向您合并的节点。运行la的析构函数时,这些节点将被删除。

如果在合并后将la的头节点设置为NULL指针(la.head = nullptr),则在析构函数运行时,将没有任何节点可以删除。

一个不相关的问题是,在创建新列表(例如cmp)时,您不会在地方复制split

关于c++ - 连接超过1个节点的子列表时,链表合并合并的C++实现失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57735047/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com