beginnerprogrammer
beginnerprogrammer

Reputation: 183

linked list and reading from text file

I am a beginner to all this c++ programming, and I want to try one last time to get help. I have to create a method called add_aa( str ) that adds str in ascending alphabetical order. This involves using linked list and reading from a text file. In the text file, the following is in it: aa hi dd hi. that is all that is in the text file. in the main.cpp file here is a code that would out hi to str:

if( cmds.first == "aa" )            // add alphabetically
    {
        lst.add_aa( cmds.second );
    }

with this code, reading from the text file, hi would be outputted and assigned to str. I am trying to write the method that does this, but i cannot come up with anything. also, this is the output that i should receive to clear things up. enter image description here

my code so far is just:

inline void LList:: add_aa(const string _str)
{
cout << "\tp = " << std::hex << p << std::dec << '\n';
}

Upvotes: 0

Views: 4694

Answers (1)

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145459

oh my, we're into the C++ “linked list tutorial” that i said was impractical the last time you asked.

but maybe i have time to write it.

i will make myself some tea first, and post this in small installments, one at a time.


Basic singly linked list.

A basic singly linked list consists of nodes, where each node contains a pointer to the next node in the list, e.g. called next. By convention next==0 indicates the end of the list. So, let’s create such a simple list, traverse it (here just displaying the contents), and clean up by deallocating the nodes:

#include <iostream>
using namespace std;

struct node_t
{
    node_t*     next;
    int         value;

    node_t( int const v = 0 )
        : next( 0 ), value( v )
    {}
};

int main()
{
    char const blah[] = "moonflower";

    node_t*     first   = 0;

    // Create the list.
    for( auto ch : blah )
    {
        node_t* node = new node_t( ch );

        node->next = first;     // After this `node` points to the new first node.
        first = node;           // Now `first` also points to the new first node.
    }

    // Display it
    for( node_t* p = first;  p != 0;  p = p->next )
    {
        cout << p->value << " ";
    }
    cout << endl;

    // Delete the list
    while( first != 0 )
    {
        node_t* const doomed = first;

        first = first->next;
        delete doomed;
    }
}

For the cleaning up at the end it’s important to do things in the right order, so as to not access a deleted node. Accessing a deleted node might work. But then, due to Murphy’s law, it will fail later on, at the point in time where it’s absolutely most critical that things shoudl work.

Output:

0 114 101 119 111 108 102 110 111 111 109

These are the ASCII character codes, including the string’s terminating zero.

Note the order of the numbers! :-)


Insertion in a singly linked list.

A main advantage of a linked list, compared to direct use of an array, is that inserting a new node takes constant time. With the direct use of an array, an insertion of a new item has to move all items after it (or before), which takes time proportional to the array size. However, note that (1) the time to find the insertion point is generally forced to linear in list size, while it can be logarithmic for the array, and (2) by use of the technique called “insertion gap” or “cursor gap” insertion of an item can be constant time also for an array (at some slight costs).

In order to insert a number in ascending sorted position, with the basic list you need a pointer to the node before the insertion point, so that that node’s next can be updated to point to the new node.

Since with the basic list there is no such node before the first node, insertion at the start of the list becomes an ugly special case, as illustrated below:

#include <iostream>
using namespace std;

struct node_t
{
    node_t*     next;
    int         value;

    node_t( int const v = 0 )
        : next( 0 ), value( v )
    {}
};

int main()
{
    char const blah[] = "moonflower";

    node_t*     first   = 0;

    // Create the list.
    for( auto ch : blah )
    {
        node_t* node = new node_t( ch );

        // Find the insertion point.
        node_t* p = first;
        node_t* p_node_before = 0;
        while( p != 0 && p->value < ch )
        {
            p_node_before = p;
            p = p->next;
        }

        if( p_node_before == 0 )
        {
            // Insert at start of list, just like in the first program.
            node->next = first;     // After this `node` points to the new first node.
            first = node;           // Now `first` also points to the new first node.
        }
        else
        {
            // Insert within the list or at the end of the list.
            node->next = p_node_before->next;
            p_node_before->next = node;
        }
    }

    // Display it
    for( node_t* p = first;  p != 0;  p = p->next )
    {
        cout << p->value << " ";
    }
    cout << endl;

    // Delete the list
    while( first != 0 )
    {
        node_t* const doomed = first;

        first = first->next;
        delete doomed;
    }
}

Output:

0 101 102 108 109 110 111 111 111 114 119

Note that due to the search for an insertion place, each insertion on average takes time proportional to the final length of the list, so that in total this is quadratic time, O(n2). It’s a simple insertion sort. I hope I did not introduce any bug! :-)


The ingenious pointer-to-pointer.

One way to reduce the mess for insertion at the start of a basic list, is to note that you really only need access to the next field of the node before the insertion point.

You do not need access to the complete node before the insertion point.

And so you can make do with just a pointer to the next pointer. I.e. a pointer to pointer. And for the start of the list, the first pointer variable can serve nicely as if it was the next field in some node before that, so initially the pointer-to-pointer can just point to the first pointer (ahem), and then it can be moved on the next pointers, like this:

#include <iostream>
using namespace std;

struct node_t
{
    node_t*     next;
    int         value;

    node_t( int const v = 0 )
        : next( 0 ), value( v )
    {}
};

int main()
{
    char const blah[] = "moonflower";

    node_t*     first   = 0;

    // Create the list.
    for( auto ch : blah )
    {
        node_t* node = new node_t( ch );

        // Find the insertion point.
        node_t** p_next_before = &first;
        while( *p_next_before != 0 )
        {
            node_t* const next_node = *p_next_before;
            if( next_node->value >= ch )
            {
                break;
            }
            p_next_before = &next_node->next;
        }

        // Insert at start of list, just like in the first program.
        node->next = *p_next_before;    // After this `node` points to the new first node.
        *p_next_before = node;         // Now ... also points to the new first node.
    }

    // Display it
    for( node_t* p = first;  p != 0;  p = p->next )
    {
        cout << p->value << " ";
    }
    cout << endl;

    // Delete the list
    while( first != 0 )
    {
        node_t* const doomed = first;

        first = first->next;
        delete doomed;
    }
}

The output is still

0 101 102 108 109 110 111 111 111 114 119

as it should be.

At first I wrote that code with a rather long continuation condition directly in the while loop head. But following the rule that one should name just about everything in sight, I moved part of that inside the loop, then with a break statement. So that’s the reason for the break statement: to support the name next_node.

In C++ the pointer-to-pointer is sometimes represented as a reference-to-pointer. For example, a function to unlink a node from a basic list, might take a reference-to-pointer argument, instead of the C style pointer-to-pointer. I find that a bit cleaner, when it is applicable.


Using a header node.

An alternative to the pointer-to-pointer is to introduce a dummy node before the first real node. Then even an empty list has one physical node, namely the dummy node. The dummy node is usually called a header node.

After the compiler has finished with its translation to machine code and optimization, code that uses a header node may be identical to code that uses the pointer-to-pointer technique, except that the header node involves one more allocation, and takes a little more room than a single pointer variable. But the difference is about how one thinks about the code, how one understands it. Conceptually having a header node is very different from using the pointer-to-pointer technique – e.g. if you draw the list structure, then the list with header node looks manifestly different from the one without such a node.

Anyway, code:

#include <iostream>
using namespace std;

struct node_t
{
    node_t*     next;
    int         value;

    node_t( int const v = 0 )
        : next( 0 ), value( v )
    {}
};

int main()
{
    char const blah[] = "moonflower";

    node_t* header_node = new node_t();

    // Create the list.
    for( auto ch : blah )
    {
        node_t* node = new node_t( ch );

        // Find the insertion point.
        node_t* p_before = header_node;
        while( p_before->next != 0 && p_before->next->value < ch )
        {
            p_before = p_before->next;
        }

        // Insert at start of list, just like in the first program.
        node->next = p_before->next;    // After this `node` points to the new first node.
        p_before->next = node;          // Now ... also points to the new first node.
    }

    // Display it
    for( node_t* p = header_node->next;  p != 0;  p = p->next )
    {
        cout << p->value << " ";
    }
    cout << endl;

    // Delete the list
    auto& first = header_node;          // Just a renaming for clarity.
    while( first != 0 )
    {
        node_t* const doomed = first;

        first = first->next;
        delete doomed;
    }
}

And as before the output is still

0 101 102 108 109 110 111 111 111 114 119

as it should be.

Upvotes: 7

Related Questions