Reputation: 2708
I am trying to find the middle element of a double linked list in constant time complexity . I came across the following http://www.geeksforgeeks.org/design-a-stack-with-find-middle-operation/ solution. But I don't understand how to use the middle pointer. Can anyone please help me understand this or give me a better solution .
Upvotes: 0
Views: 2078
Reputation: 853
I've re-written this code in C++ for explanation purposes:
#include <iostream>
typedef class Node* PNode;
class Node{
public:
PNode next;
PNode prev;
int data;
Node(){
next = nullptr;
prev = nullptr;
data = 0;
}
};
class List{
private:
//Attributes
PNode head;
PNode mid;
int count;
//Methods
void UpdateMiddle( bool _add );
public:
//Constructors
List(){
head = nullptr;
mid = nullptr;
count = 0;
}
~List(){
while( head != nullptr ){
this->delmiddle();
std::cout << count << std::endl;
}
}
//Methods
void push( int _data );
void pop();
int findmiddle();
void delmiddle();
};
void List::UpdateMiddle( bool _add ){
if( count == 0 ){
mid = nullptr;
}
else if( count == 1 ){
mid = head;
}
else{
int remainder = count%2;
if(_add){
if( remainder == 0 ){
mid = mid->prev;
}
}
else{
if( remainder == 1 ){
mid = mid->next;
}
}
}
}
void List::push( int _data ){
PNode new_node = new Node();
new_node->data = _data;
new_node->prev = nullptr;
new_node->next = head;
if( head != nullptr ) head->prev = new_node;
head = new_node;
count++;
UpdateMiddle( true );
}
void List::pop(){
if( head != nullptr ){
PNode del_node = head;
head = head->next;
if( head != nullptr ) head->prev = nullptr;
delete del_node;
count--;
UpdateMiddle(false);
}
else if( count != 0 ){
std::cout << "ERROR";
return;
}
}
int List::findmiddle(){
if( count > 0 ) return mid->data;
else return -1;
}
void List::delmiddle(){
if( mid != nullptr ){
if( count == 1 || count == 2){
this->pop();
}
else{
PNode del_mid = mid;
int remainder = count%2;
if( remainder == 0 ){
mid = del_mid->next;
mid->prev = del_mid->prev;
del_mid->prev->next = mid;
delete del_mid;
count--;
}
else{
mid = del_mid->prev;
mid->next = del_mid->next;
del_mid->next->prev = mid;
delete del_mid;
count--;
}
}
}
}
The push and pop functions are self-explanatory, they add nodes on top of the stack and delete the node on the top. In this code, the function UpdateMiddle
is in charge of managing the mid
pointer whenever a node is added or deleted. Its parameter _add
tells it whether a node has been added or deleted. This info is important when there is more than two nodes.
Note that when the UpdateMiddle
is called within push
or pop
, the counter has already been increased or decreased respectively. Let's start with the base case, where there is 0 nodes. mid
will simply be a nullptr
. When there is one node, mid
will be that one node.
Now let's take the list of numbers "5,4,3,2,1". Currently the mid is 3 and count
, the amount of nodes, is 5 an odd number. Let's add a 6. It will now be "6,5,4,3,2,1" and count
will now be 6 an even number. The mid
should also now be 4, as it is the first in the middle, but it still hasn't updated. However, now if we add 7 it will be "7,6,5,4,3,2,1", the count
will be 7, an odd number, but notice that the mid
wont change, it should still be 4.
A pattern can be observed from this. When adding a node, and count
changes from even to odd, the mid
stays the same, but from odd to even mid
changes position. More specifically, it moves one position to the left. That is basically what UpdateMiddle
does. By checking whether count
is currently odd or even after adding or deleting a node, it decides if mid
should be repositioned or not. It is also important to tell whether a node is added or deleted because the logic works in reverse to adding when deleting. This is basically the logic that is being applied in the code you linked.
This algorith works because the position of mid
should be correct at all times before adding or deleting, and function UpdateMiddle
assumes that the only changes were the addition or deletion of a node, and that prior to this addition or deletion the position of mid was correct. However, we make sure of this by making the attributes and our function UpdateMiddle
private, and making it modifiable through the public functions.
Upvotes: 1
Reputation: 1170
The trick is that you don't find it via a search, rather you constantly maintain it as a property of the list. In your link, they define a structure that contains the head node, the middle node, and the number of nodes; since the middle node is a property of the structure, you can return it by simply accessing it directly at any time. From there, the trick is to maintain it: so the push
and pop
functions have to adjust the middle node, which is also shown in the code.
More depth: maintaining the middle node: we know given the count that for an odd number of nodes (say 9), the middle node is "number of nodes divided by 2 rounded up," so 9/2 = 4.5 rounded up = the 5th node. So if you start with a list of 8 nodes, and add a node, the new count is 9, and you'll need to shift the middle node to the "next" node. That is what they are doing when they check if the new count is even.
Upvotes: 1