Better to use a grounded list, or no? - c++

I'm a csci student at Colorado Mesa University. The department head teaches a grounded method for linked lists:
struct nodeType
{
int id;
nodeType *link;
};
void createList(nodeType *&head, nodetype *&tail)
{
head = new nodetype;
tail = new nodetype;
head->id=-1; //some initialize value
head->link=tail;
tail->link=NULL;
}
void insertList(nodeType *&head, nodeType *&tail)
{
nodetype *knew,*prior, *next;
knew = new nodetype;
knew ->name = name
prior = head;
next = head->link;
while(next != tail && knew->id > next->id)
{
prior = next;
next = next->link;
}
prior->link = knew;
knew->link = next;
}
She teaches this for obvious reasons. With the grounded head and tail, It's easier to insert from, as you call the above function, and then write a function to append all the data inside those two nodes, and it's slightly easier when writing your delete function, as you never delete head or tail, and thus it makes it harder to lose the list and create garbage.
My algorithms professor says that everywhere else that I encounter lists "in the real world," a non-grounded list would be better. Other languages, using the STL and on the internet, I wouldn't find list functions that implement a head and tail.
I just want to be prepared for programming in the actual real world, not what my professors think is the real world, so my question is this: Is it better to use one or the other, to use whichever I find easier, or to approach each problem with both in mind?
Thank you in advance for your time helping me resolve this feud.

In "the real world", you will use a list that other programmers have designed, implemented, optimized, and tested, and you won't ever know whether it is "grounded", since that is just an implementation detail.
What's important to take away from your algorithms courses is:
Performance characteristics. Does a linked-list or vector have faster random-access? Faster append? Faster removal of the first element? Using the right container is NOT premature optimization.
Seeing enough different implementation styles so that if you ever have to step through your code with a debugger, you won't be royally confused. If you saw the at-end-of-list test return true, but the next-node pointer isn't NULL, you'd probably be really confused if you'd never seen a "grounded" list implementation before.

Related

Reverse a linked list, is my idea correct?

I was reading this: https://www.geeksforgeeks.org/reverse-a-linked-list/
I think I found an easier answer but since it wasn't written their and they used more complicated one I think something is wrong with mine and can't figure it out.
We start from the first node which we will copy and insert it into a new list.
then we go one step to the right, copy the value, create a new list with that value and setting its right the previous list and so on.
What's wrong with my algorithm?
If I interpret your idea correctly, it is something like this:
void forward_list::reverse() {
forward_list new_list;
for(auto& v : *this)
new_list.emplace_front(std::move(v));
std::swap(new_list, *this); // or *this = std::move(new_list);
}
... and this would work. It even looks pretty nice I'd say.
What's wrong with my algorithm?
It's creates a new node for every old node and then has to copy/move the data from the old node to the new node. The old nodes will then be destroyed.
Some types aren't even copyable or moveable so this reverse algorithm couldn't be used with such types.
It invalidates references and iterators.
Consider the alternative, reversing the links. It's a bit more complex but gets the job done without any of the drawbacks mentioned above.
Here's my take on reversing the links, which is not implemented exactly the same way as in the link you shared but it works pretty much in the same way. I think this one has fewer assignments though.
curr will point one step ahead of head and next is used to save the next pointer when the relinking is being done.
void forward_list::reverse() {
if(head) { // must have at least one node
node* curr = head->next; // head + 1
head->next = nullptr; // this will be the new last node
node* next; // for saving next while relinking
while(curr) { // while curr != nullptr
next = curr->next; // save the next pointer
curr->next = head; // relink backwards
head = curr; // move head forward
curr = next; // move curr forward
}
// head now points at the new start of the list automatically
}
}
Your algorithm is functionally correct, but since you are creating an entirely new list instead of reversing the existing nodes in-place, you are using twice the memory. You also have to deal with the cleanup of deleting the old nodes once you have your new list.

Why do we need, One parameter is required in adding a node in binary tree; where as two parameters are required in adding a node in linked list?(C++)

So as a novice programming I am trying to learn data structures and a question came in my mind while I was working on Binary trees. So the code/function to add a node to binary tree is:
//binary tree node addition
struct tree* addnode(int rdata)
{
struct tree* temp = new tree();
temp->data = rdata;
temp->right = NULL;
temp->left = NULL;
return temp;
}
Here we can see that there is only one parameter required in the addition of node i.e. we don't pass the root address in the function. But in addition of linked list the addition of node is at any place (Beginning, ending or after k nodes) has two parameters which are a headref pointer of linked list and the data value. Like code for adding a node in the starting of the linked list is :
void addatstartLL(struct LL** headref, int rdata) {
struct LL* temp = new LL();
temp->data = rdata;
temp->next = (*headref);
(*headref) = temp;
}
The above two codes are applied like this :
#include<iostream>
using namespace std;
struct LL {
int data;
struct LL* next;
};
struct tree {
int data;
struct tree* left;
struct tree* right;
};
int main()
{
struct tree* root = new tree();
root->data = 1;
root->left=addnode(2);
struct LL* head = NULL;
addatstartLL(&head, 2);
}
So, my question here why do we need only one parameter in binary tree (only data and not the root address cause) and two parameters in linked list i.e. headref and data? Why don't we write the same kind of function for both of the data structures? Thank you in advanced.
TLDR They are badly written functions.
Those functions are what you make them to be. If you make one take one argument and the other take 2 arguments then that's what it is. They are not standard functions and they are not well written functions either (in terms of both interface and implementation).
There are many problems with the code you have:
addnode doesn't actually add a node to a list. It just creates a node. That's why it takes one argument.
LL structure doesn't represent a linked list. It represents a node.
You use owning raw pointers everywhere so there is no clear owner of the memory allocated by new. You code will leak at the first exception, even if you explicitly delete the nodes. That's why you need to religiously follow RAII concept in C++.
struct tree* root = new tree(); There is absolutely no reason to dynamically allocate tree there in main. tree root{} would suffice.
structures are used in the most minimalist way possible, just the bare C capable way. In C++ - true C++ - you would use constructors, encapsulation, methods and so on.
This is by far not idiomatic, correct C++ code. It's C code (sprinkled with C++ IO) and compiled with a C++ compiler. If that's what your teacher requires then by all means write this for him/her to make them happy, but be aware that it's definitely not how you write correct, clean, idiomatic, modern C++ code. If you lean this from a tutorial or book then ditch it immediately and learn from a good C++ resource.
Your first function doesn't add a node to a tree. It creates a new tree of one node.
It can be used by a function that adds a node to a tree, once the location to add it is determined.
Your second function is adding a node to a list at a specific position. Comparable tree functions would be
void addnodebefore(tree** root, int rdata)
{
tree* temp = new tree();
temp->data = rdata;
temp->right = *root;
temp->left = nullptr;
*root = temp;
}
void addnodeafter(tree** root, int rdata)
{
tree* temp = new tree();
temp->data = rdata;
temp->right = nullptr;
temp->left = *root;
*root = temp;
}
There are STL containters similar to what you have, std::set is usually implemented as some type of sorted tree, while std::list is usually implemented as a circular doubly linked list with a dummy node used for the head and tail of the list.
To add a new element to std::set, std::set::insert(value) can be used, and it is a single parameter call.
To add a new element to std::list, std::list::push_front(value) or std::list::push_back(value) can be used, and they are single parameter calls.
Although these are single parameter calls, the container itself could be considered to be similar to having a second parameter. In other words, you could create static functions (not tied to a specific instance of a container) for insert, push_front, or push_back, that would take an instance of the container as one the parameters.

Finding a Node in a tree of Nodes with an arbitrary number of children

So I have this group project that's supposed to take in a text file, chop it up into species and sub species and that sort of thing. I've finished the chopping and the tree structure but I'm having a hard time getting my BuildGraph() to work. I've narrowed it down to the findNode() function that right now looks like
EDIT: comments, Also this is my first time posting so sorry if it's ugly.
I had both of these changes in a different version but ended up getting rid of them somewhere?
Node* findNode(std::string data, Node* head){
if (head == NULL){
return NULL;
}
else if(head->data == data){
return head;
} else {
if(head->children[0]!=NULL){
for(int i = 0; i<head->children.size();i++){
return findNode(data, head->children.at(i));
}
}
}
My Node structure looks like this...
public:
std::string data;
std::vector <Node*> children;
Node(std::string data){
this->data=data;
}
I'm pretty sure that the problem that I'm running into is something about the recursive call is going deeper rather than expanding somehow causing a segfault.
Can someone tell me if what I'm looking to do is possible?
You have 2 problems:
if(head->children[0]!=NULL){
You access children[0] but you don't check if children is empty. I'm fairly sure this causes your SegFault.
return findNode(data, head->children.at(i));
You need to check if this is null before returning. In case it's null you want to check the other children.
Also pass const std::string& data so that you don't copy the string at every call.

reverse linked list using recursion

I am trying to reverse a linked list using recursion. I made the reverse() function to reverse the list. I created a linked list in main() and also defined print() method.
I don't know what mistake I am making. Please help me correct it. The code snippets are given below.
struct node
{
int data;
struct node *next;
}*head;
void reverse(node **firstnode,node *n)
{
if(n==NULL)
{
head=n;
return;
}
reverse(&head,n->next);
struct node *q=n->next;
n->next=q;
q->next=NULL;
}
void main()
{
......
head=first;
reverse(&first,first);
print(head);
}
It may not address your question directly. However, you mentioned C++11 in the tags. So, take look at std::forward_list. It is a standard container that is based on single linked-list.
List* recur_rlist(List* head)
{
List* result;
if(!(head && head->next))
return head;
result = recur_rlist(head->next);
head->next->next = head;
head->next = NULL;
return result;
}
void printList(List* head)
{
while(head != NULL) {
std::cout<<head->data<<" ";
head = head->next;
}
}
void main()
{
List* list = createNode(2);
append(list, createNode(3));
append(list, createNode(4));
append(list, createNode(5));
append(list, createNode(6));
List* revlist = recur_rlist(list);
printList(revlist);
}
I think you mixed up your addressing at the end of the reverse function, it should probably look like:
q->next=n;
n->next=NULL;
Also, I am not sure if you need the "firstnode" argument.
Since you want to understand the code, and you have several great resources with finished code already, more finished code examples aren't needed. I'll just answer with some concepts and point you at the errors you need to fix.
First, some background concepts.
Linked lists: first and rest
Any linked list is either empty, or can be broken down into first (a node) and rest (a smaller linked list, or empty). This makes recursion much easier.
if (head){
node * first = head;
node * rest = head->next;
}
Invariant (simplified): A guarantee that is always true at the start and end of your function.
In a linked list, you expect that head points to a node, which points to another node, and so forth, until you get to the end, which is signaled by a nullptr. All of the nodes are different. All of the nodes are valid. These are the guarantees that must be true before you call your function and when your function returns.
In a recursive function, the invariants must hold on the sublist you are reversing at every step, because you return from the function at every step. But this makes recursion much easier because all you have to do is make sure that if the input is good, then your function will return a good value at the current step.
End of recursion:
You can prove that your recursive function never gets in an infinite loop by combining the previous concepts. If the invariants hold, then each step will work, and because each recursive call will take rest, which is guaranteed to be either nullptr or a shorter list, eventually we have to reach the end. And of course show that you handle the end.
Okay, on to the actual problems:
You don't handle end of recursion correctly. You just set head=nullptr at the end, and I'm pretty sure that's not what you want for head. You may want to handle the end if (nullptr == n->next), because then you know that is the last node. Of course, you still have to correctly handle the trivial case where nullptr==head.
You don't preserve invariants. You tried, but it looks like your bookkeeping is just all wrong. I suggest using the debugger or 3x5 notecards to step through what you're actually doing to fix the actual work of swapping things around. For example, it looks like you just confused which node is which in this code snippet:
struct node *q=n->next; // n is first, q is rest
// what if nullptr == q?
n->next=q; // n->next = n->next doesn't actually change anything
q->next=NULL; // this must already be true if reverse(rest) did its job
// q and n were swapped?
Also, your function takes "firstnode" but does not use it, but instead sets the global variable "head" as a side effect.

How could I create a list in c++?

How can I create a list in C++? I need it to create a linked list. How would I go about doing that? Are there good tutorials or examples I could follow?
I take it that you know that C++ already has a linked list class, and you want to implement your own because you want to learn how to do it.
First, read Why do we use arrays instead of other data structures? , which contains a good answer of basic data-structures. Then think about how to model them in C++:
struct Node {
int data;
Node * next;
};
Basically that's all you need to implement a list! (a very simple one). Yet it has no abstractions, you have to link the items per hand:
Node a={1}, b={20, &a}, c={35, &b} d={42, &c};
Now, you have have a linked list of nodes, all allocated on the stack:
d -> c -> b -> a
42 35 20 1
Next step is to write a wrapper class List that points to the start node, and allows to add nodes as needed, keeping track of the head of the list (the following is very simplified):
class List {
struct Node {
int data;
Node * next;
};
Node * head;
public:
List() {
head = NULL;
}
~List() {
while(head != NULL) {
Node * n = head->next;
delete head;
head = n;
}
}
void add(int value) {
Node * n = new Node;
n->data = value;
n->next = head;
head = n;
}
// ...
};
Next step is to make the List a template, so that you can stuff other values (not only integers).
If you are familiar with smart pointers, you can then replace the raw pointers used with smart pointers. Often i find people recommend smart pointers to starters. But in my opinion you should first understand why you need smart pointers, and then use them. But that requires that you need first understand raw pointers. Otherwise, you use some magic tool, without knowing why you need it.
You should really use the standard List class. Unless, of course, this is a homework question, or you want to know how lists are implemented by STL.
You'll find plenty of simple tutorials via google, like this one. If you want to know how linked lists work "under the hood", try searching for C list examples/tutorials rather than C++.
If you are going to use std::list, you need to pass a type parameter:
list<int> intList;
list<int>* intListPtr = new list<int>;
If you want to know how lists work, I recommending googling for some C/C++ tutorials to gain an understanding of that subject. Next step would then be learning enough C++ to create a list class, and finally a list template class.
If you have more questions, ask back here.
Why reinvent the wheel. Just use the STL list container.
#include <list>
// in some function, you now do...
std::list<int> mylist; // integer list
More information...
I'm guessing this is a homework question, so you probably want to go here. It has a tutorial explaining linked lists, gives good pseudocode and also has a C++ implementation you can download.
I'd recommend reading through the explanation and understanding the pseudocode before blindly using the implementation. This is a topic that you really should understand in depth if you want to continue on in CS.
Boost ptr_list
http://www.boost.org/doc/libs/1_37_0/libs/ptr_container/doc/ptr_list.html
HTH
Create list using C++ templates
i.e
template <class T> struct Node
{
T data;
Node * next;
};
template <class T> class List
{
Node<T> *head,*tail;
public:
void push(T const&); // push element
void pop(); // pop element
bool empty() // return true if empty.
};
Then you can write the code like:
List<MyClass>;
The type T is not dynamic in run time.It is only for the compile time.
For complete example click here.
For C++ templates tutorial click here.
We are already in 21st century!!
Don't try to implement the already existing data structures.
Try to use the existing data structures.
Use STL or Boost library