我试图在恒定的时间复杂度中找到双链表的中间元素。 我遇到了以下http://www.geeksforgeeks.org/design-a-stack-with-find-middle-operation/解决方案。 但我不明白如何使用中间指针。 任何人都可以帮助我理解这一点或给我一个更好的解决方案。
答案 0 :(得分:1)
诀窍在于,您不会通过搜索找到,而是将其作为列表的属性进行持续维护。在链接中,它们定义了一个包含头节点,中间节点和节点数的结构;由于中间节点是结构的属性,因此您可以随时直接访问它来返回它。从那里开始,诀窍是维护它:所以push
和pop
函数必须调整中间节点,这也显示在代码中。
更深入:维护中间节点:我们知道给定计数对于奇数个节点(比如9),中间节点是“节点数除以2向上舍入”,因此9/2 = 4.5舍入up =第5个节点。因此,如果您从一个包含8个节点的列表开始,并添加一个节点,则新计数为9,您需要将中间节点移至“下一个”节点。这就是他们在检查新计数是否均匀时所做的事情。
答案 1 :(得分:1)
为了便于说明,我用C ++重写了这段代码:
#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--;
}
}
}
}
推送和弹出功能是不言自明的,它们在堆栈顶部添加节点并删除顶部的节点。在此代码中,只要添加或删除节点,函数UpdateMiddle
就负责管理mid
指针。其参数_add
告诉它是否已添加或删除节点。当有两个以上的节点时,此信息很重要。
请注意,在UpdateMiddle
或push
内调用pop
时,计数器已分别增加或减少。让我们从基本情况开始,其中有0个节点。 mid
只是nullptr
。当有一个节点时,mid
将是该节点。
现在让我们来看一下数字列表&#34; 5,4,3,2,1&#34;。目前中间是3和count
,节点数量是5个奇数。让我们添加一个6.它现在将是&#34; 6,5,4,3,2,1&#34;而count
现在将是一个偶数。 mid
现在也应该是4,因为它是中间的第一个,但它仍然没有更新。但是,现在如果我们添加7将是&#34; 7,6,5,4,3,2,1和#34;,count
将是7,一个奇数,但请注意{ {1}}不会改变,它应该仍然是4.
可以从中观察到一种模式。添加节点时,mid
从偶数更改为奇数,count
保持不变,但从奇数到偶数mid
更改位置。更具体地说,它向左移动一个位置。这基本上是mid
的作用。通过检查UpdateMiddle
当前是奇数还是偶数添加或删除节点后,它会决定是否应重新定位count
。判断是添加还是删除节点也很重要,因为逻辑与删除时的添加相反。这基本上是您链接的代码中应用的逻辑。
此算法有效,因为mid
的位置在添加或删除之前应始终正确,而函数mid
假定唯一的更改是添加或删除节点,以及对于这个添加或删除,mid的位置是正确的。但是,我们通过将属性和函数UpdateMiddle
设为私有来确保这一点,并通过公共函数使其可修改。