Tarjan的伪代码如何工作(向熟悉C或Java的人解释)?


40

短篇小说

多年前,著名的计算机科学家Tarjan写了一本书。它包含绝对奇怪的伪代码。有人可以解释一下吗?

长篇小说

塔里扬(Tarjan)以许多成就而闻名,包括他是八角的共同发明者。他在1980年代出版了《数据结构和网络算法》一书。

Tarjan书中的所有伪代码都是用他自己设计的语言编写的。伪代码约定非常严格。这几乎是一种真正的语言,可以想象为它编写一个编译器。Tarjan写道,他的语言基于以下三种语言:

我希望熟悉以上一种或两种语言或Tarjan作品的人能够回答我的问题。

用Tarjan语言编写的函数示例如下所示:

heap function mesh (heap nodes h1, h2);

    if key(h1) > key(h2) → h1 ⟷  h2 fi;

    right (h1) := if right(h1) = null → h2

                  |right(h1) ≠ null → mesh (right(h1), h2) fi;

    if rank (left (h1)) < rank (right (h1)) → left(h1) ⟷ right(h1) fi;

rank (h1) := rank(right(h1)) + 1;

return h1,

end mesh;

我看过很多伪代码,但是我从未见过像Tarjan一样的东西。Tarjan的伪代码如何工作?如何将Tarjan伪代码的示例重写为看起来更像C或Java的东西?它甚至不必是C或Java。Tarjan语言中的if-else构造不仅与C系列语言不同,而且与Python,MATLAB和许多其他语言不同。


6
具体来说,您不了解什么?书中对语法和语义有什么解释?
拉斐尔

8
您是从某个地方复制样本还是自己转录?函数体内的最后两行是否真的没有缩进更多?并执行return语句真正以逗号结束?
Bergi

Answers:


63

目录

我将对Tarjan伪代码的解释分为以下几部分:

  1. Tarjan的If-else块(->|运算符)
  2. 分配和平等测试(:==
  3. else if,但没有else构造
  4. Tarjan的条件赋值运算符 := if
  5. Tarjan if:= if
    5.5的其他示例Tarjan阵列(或列表)

  6. 运营商摘要

  7. Tarjan的双指向箭头运算符(
  8. Tarjan的do循环就像C / Java while循环
  9. Tarjan的条件赋值运算符具有所有错误条件

(1)塔里安的If-else积木

(运营商|

if-else构造可能是Tarjan语言中最基本的控制结构。除了类似C的if块,Tarjan的赋值和Tarjan的while循环几乎都内置了if-else行为。Tarjan的箭头运算符->(或→)是if语句的条件与if语句的执行块之间的分隔符。

例如,用塔里安的语言,我们可能有:

# Example One
if a = 4 → x := 9 fi    

如果我们将上面的Tarjan代码行部分转换为C或Java,则会得到以下结果:

if (a = 4)
    x := 9
fi 

Tarjan代替了右花括号(如C和Java中的花括号),以-block结尾if,其关键字类似于ALGOL的向后拼写:fi

如果继续翻译上面的示例,则会得到:

if (a = 4) {
    x := 9
}

(2)分配和平等测试(:==

Tarjan从ALGOL(后来在Pascal中也看到了)带走了这些运算符。

Tarjan =用于相等性测试,而不是分配(因此,它像Java一样工作==)。

对于分配,Tarjan使用了:=与Java类似的功能=

因此,如果我们继续翻译我们的示例,我们将:

if (a == 4) {
    x = 9
}

|Tarjan语言中的竖线(或“竖线”或)等效else if于C或Java中的关键字。
例如,用塔里安的语言,我们可能有:

# Example Two
if a = 4 → x := 9 |  a > 4  → y := 11 fi 

上面的Tarjan代码可转换为:

if (a == 4) {
    x = 9
}
else if (a > 4)  {
    y = 11
}

(3)else ifelse构造

之前,我if没有描述细微差别,而是介绍了- 语句的基础。但是,我们不会讨论一个小细节。Tarjan-ian if-else块中的最后一个子句必须始终包含arrow()运算符。因此else,只有Tarjan语言没有else ifelse用Tarjan的语言最接近-block的是使最右边的test-condition true

if a = 4 → x := 9 |  a > 4  → y := 11 | true  → z := 99  fi

在C / Java中,我们将有:

if (a == 4) {
    x = 9
}

else if (a > 4)  {
    y = 11
}
else { // else if (true)
    z = 99
} 

实例比一般说明更容易理解。但是,现在我们有了一些示例,可以知道,Tarjan的if-else构造的一般形式如下:

if condition
    → stuff to do
 | condition
    → stuff to do
 [...] 
 | condition 
    → stuff to do
fi       

人物 |就像if else

角色将测试条件与待办事项分开。

(4)塔里安的条件赋值运算符 := if

if可以使用两种非常不同的方式来使用Tarjan 。到目前为止,我们仅描述了Tarjanian的用途之一if。令人困惑的是,Tarjan仍然if对第二种类型的if-construct 使用符号/语法。使用哪个if基于上下文。实际上,分析上下文非常容易,因为第二种Tarjan- if总是由赋值运算符预先确定。

例如,我们可能有以下Tarjan代码:

# Example Three
x := if a = 4 → 9 fi 

开始离题

在使用Tarjan代码一段时间后,您便习惯了操作顺序。如果我们在上面的示例中加上测试条件,我们将获得:

x := if (a = 4) → 9 fi 

a = 4不是分配操作。a = 4就像a == 4-它返回true或false。

终点离题

可以将其:= if视为单个运算符的语法,这与:=和有所不同if。事实上,我们将:= if运算符称为“条件赋值”运算符。

对于if我们列出(condition → action)。因为:= if我们列出了右侧值(condition → value)在哪里,value我们可以将其分配给左侧lhs

# Tarjan Example Four
lhs := if (a = 4) → rhs fi 

在C或Java中可能看起来像:

# Example Four
if (a == 4) {
    lhs = rhs
}

考虑下面的Tarjanian代码中的“条件赋值”示例:

#示例5的Tarjan实例化x:= a = 4→9 | a> 4→11 | 真实→99 fi

在C / Java中,我们将有:

// C/Java Instantiation of Example Five
if (a == 4) {
    x = 9
}
else if (a > 4)  {
    x = 11
}
else if (true) { // else
    x = 99
} 

(5)运营商摘要:

到目前为止,我们有:

  • :=......赋值运算符(C / Java =

  • =......平等测试(C / Java ==

  • ...... if块的测试条件与if块的主体之间的分隔符

  • | ..... C / Java else-if

  • if ... fi ..... if-else块

  • := if... fi .....基于if-else块的条件赋值

(5.5)Tarjan列表/数组:

Tarjan的语言具有内置的数组式容器。Tarjan数组的语法比Tarjan if else语句的符号直观得多。

list1  := ['lion', 'witch', 'wardrobe'];
list2a := [1, 2, 3, 4, 5];
list2b := [1, 2];
list3  := ["a", "b", "c", "d"];
list4  := [ ]; # an empty array

Tarjan数组elementa用括号()而不是方括号括起来[]

索引从开始1。从而,

list3  := ["a", "b", "c", "d"]
# list3(1) == "a" returns true
# list3(2) == "b" return true 

下面显示了如何创建一个新数组,其中包含第一个元素和第五个元素 [1, 2, 3, 4, 5, 6, 7]

nums := [1, 2, 3, 4, 5, 6, 7]
new_arr := [nums(1), nums(5)]

为数组定义了相等运算符。以下代码打印true

x := false
if [1, 2] = [1, 2, 3, 4, 5] --> x := true
print(x)

Tarjan测试数组是否为空的方法是将其与空数组进行比较

arr := [1, 2]
print(arr = [ ])
# `=` is equality test, not assignment

可以创建一个子阵列的视图(不能复制),通过向操作者提供多个索引()联合..

list3  := ["a", "b", "c", "d"]

beg    := list3(.. 2)
# beg == ["a", "b"]
# beg(1) == "a"

end    := list3(3..)
# end == ["c", "d"]
# end(1) == "c"

mid    := list3(2..3)
# mid == ["b", "c"]
# mid(2) == "c"

# `list3(4)` is valid, but `mid(4)` is not 

(6)Tarjan if:= if

以下是Tarjan条件赋值(:= if)的另一个示例:

# Tarjan Example Six
a  := (false --> a | true --> b | false --> c1 + c2 |  (2 + 3 < 99) --> d)  

(true --> b)是条件为最左边的(cond --> action)子句。因此,原始分配示例六具有与以下相同的分配行为a := b

以下是到目前为止我们最复杂的Tarjan代码示例:

# Tarjan Example -- merge two sorted lists

list function merge (list s, t);

return if s =[] --> t
        | t = [ ] --> s
        | s != [ ] and t != [] and s(l) <= t(1) -->
            [s(1)]& merge(s[2..], t)
        | s != [ ]and t != [ ] and s(1) > r(l) -->
            [t(1)] & merge (s,t(2..))
       fi
end merge;

以下是Tarjan用于合并两个排序列表的代码的翻译。以下内容不完全是C或Java,但与Tarjan版本相比,它更接近C / Java。

list merge (list s, list t) { 

    if (s is empty) {
        return t;
    }
    else if (t is empty){
        return s;
    }
    else if  (s[1] <= t[1]) {
        return CONCATENATE([s[1]], merge(s[2...], t));
    else  { // else if (s[1] > t[1])
        return CONCATENATE ([t[1]], merge(s,t[2..]);
    }
}

下面是Tarjan代码的另一个示例,以及类似于C或Java的翻译:

heap function meld (heap h1, h2);

    return if h1 = null --> h2
            | h2 = null --> h1
            | h1 not null and h2 not null --> mesh (h1, h2) 
           fi
end meld;

以下是C / Java翻译:

HeapNode meld (HeapNode h1, HeapNode h2) {

    if (h1 == null) {
       return h2;
    }   
    else if (h2 == null) {
        return h1;
    } else {
        mesh(h1, h2)
    }
} // end function

(7)塔里安的双箭头运算符(<-->

下面是Tarjan代码的示例:

x <--> y    

双箭头()运算符用Tarjan的语言做什么?
好吧,Tarjan语言中的几乎所有变量都是指针。 <-->是交换操作。以下印刷品true

x_old := x
y_old := y
x <--> y
print(x == y_old) # prints true
print(y == x_old) # prints true

执行之后x <--> yx指向y用于指向y的对象,并指向x用于指向的对象。

以下是使用<-->运算符的Tarjan语句:

x := [1, 2, 3]
y := [4, 5, 6]
x <--> y 

下面是上面的Tarjan代码到其他伪代码的转换:

Pointer X     = address of array [1, 2, 3];
Pointer Y     = address of array [4, 5, 6];
Pointer X_OLD = address of whatever X points to;
X = address of whatever Y points to;
Y = address of whatever X_OLD points to; 

或者,我们可以有:

void operator_double_arrow(Array** lhs, Array** rhs) {

    // swap lhs and rhs

    int** old_lhs = 0;
    old_lhs = lhs;
    *lhs = *rhs;
    *rhs = *old_lhs;
    return;
}

int main() {

    Array* lhs = new Array<int>(1, 2, 3);
    Array* rhs = new Array<int>(4, 5, 6);
    operator_double_arrow(&lhs, &rhs);
    delete lhs;
    delete rhs;
    return 0;
} 

以下是使用运算符的Tarjan函数之一的示例:

heap function mesh (heap nodes h1, h2);
    if key(h1) > key(h2) → h1 ⟷  h2 fi;
    right (h1) := if right(h1) = null → h2
                   |right(h1) ≠ null → mesh (right(h1), h2)
                  fi;

    if rank (left (h1)) < rank (right (h1))
        → left(h1) ⟷ right(h1)
    fi;

    rank (h1) := rank(right(h1)) + 1;
    return h1;
end mesh;

下面是将Tarjan mesh函数转换为伪代码的伪代码,该伪代码不是C,但看起来更像C(相对而言)。目的是说明Tarjan 运算符的工作方式。

node pointer function mesh(node pointers h1, h2) {

    if (h1.key) > h2.key) {

         // swap h1 and h2
            node pointer temp;
            temp = h1;
            h1 = h2;
            h2 = temp;
    }

    // Now, h2.key <= h1.key   

    if (h1.right == null) {
        h1.right = h2;

    } else // h1.key != null {
        h1.right = mesh(h1.right, h2);
    }



    if (h1.left.rank < h1.right.rank ) {
        // swap h1.left and h1.right

        node pointer temp;
        temp = h1;
        h1 = h2;
        h2 = temp;
    }

    h1.rank = h1.right.rank + 1;
    return h1;
}    

(8)Tarjan的do循环就像C / Java while循环

Tarjan的语言iffor构造对于C / Java程序员是熟悉的。但是,while循环的Tarjan关键字是do。全do循环以关键字结尾,该关键字od是的反向拼写do。下面是一个示例:

sum := 0
do  sum < 50 → sum := sum + 1 

在C风格的伪代码中,我们有:

sum = 0;
while(sum < 50) {
    sum = sum + 1;
}

以上实际上是不正确的。Tarjan do-loop实际上是while(true)一个内部嵌套有if-else块的C / Java 。Tarjan代码的字面翻译如下:

sum = 0;
while(true) {
    if (sum < 50) {
         sum = sum + 1;
         continue;
         // This `continue` statement is questionable
    }
    break;
}

下面,我们有一个更复杂的Tarjan do-loop:

sum := 0
do  sum < 50 → sum := sum + 1 | sum < 99 → sum := sum + 5

复杂的Tarjan do-loop的C / Java风格伪代码如下:

sum = 0;
while(true) {

    if (sum < 50) {
         sum = sum + 1;
         continue;
    }
    else if (sum < 99) {
         sum = sum + 5;
         continue;
    }
    break;
}

(9)具有所有错误条件的塔里安的条件赋值运算符

尽管上面冗长的解释涵盖了大多数问题,但仍有一些问题尚未解决。我希望其他人有一天会根据我的回答写出这些改进后的答案。

值得注意的是,当使用条件赋值运算符:= if且没有条件为真时,我不是将什么值赋给变量。

x  := if (False --> 1| False --> 2 | (99 < 2) --> 3) fi

我不确定,但是有可能没有分配给x

x = 0;
if (false) {
     x = 1;
}
else if (false) {
     x = 2;
}
else if (99 < 2) {
     x = 3;
}
// At this point (x == 0)

您可能需要:= if预先声明在语句中看到的左侧变量。在那种情况下,即使所有条件都为假,该变量仍将具有一个值。

可替代地,可能全为错误的条件表示运行时错误。另一种选择是返回一个特殊null值,并将其存储null在赋值的左侧参数中。


7
我认为,仅仅实现一个解释器/翻译器和/或编写一个操作语义将是使用您的时间进行此工作的一种更有价值的方法。
Derek Elkins

2
值得注意的是,其中某些功能比其他功能更具“异国情调”。例如,可能存在=比较的意思和赋值的意思一样多的语言(如果我曾经写过一种语言,我会使其成为语法错误,并且只有:=and ==)。另一方面,交换运算符是那种只会在通用语言中出现的事物,它是一种常见的操作。但是,在其他语言中,您只需假设一个库函数称为swap并替换为h1 ⟷ h2swap(h1, h2)而不是每次都写出实现。
IMSoP

2
为什么是[1, 2] = [1, 2, 3, 4, 5]真的?
Erhannis

3
|运营商是一个后卫。它们在函数定义的Haskell(我相信其他函数语言)中使用:f x | x == 0 = 1; x == 1 = 1; otherwise = f (x-1) + f(x-2)otherwise是斐波纳契数的别名Truef定义了斐波那契数。
Bakuriu

2
@DerekElkins您为什么这么认为?与简单地用自然语言写出自己的理解(详细程度足以让其他人理解)相比,据我所知,您提到的两种活动都将花费更长的时间。尚不清楚这是否会更有价值地使用时间(特别是如果要追求的目标主要是理解)。
ShreevatsaR

7

以前没见过,但我想我可以推断出什么根据上下文的意思..想必 一定是掉期操作,并且if G1 -> S1 | G2 - >S2 | ... fi是一个的if / then / else语句型结构,它也返回一个值,如三元?:的C和Java操作。

有了这些,我们可以用类似Java的语言编写上述函数,如下所示:

HeapNode mesh(HeapNode h1, HeapNode h2)
{
  if(h1.key > h2.key)
  {
    // swap h1 and h2

    HeapNode t = h1;
    h1 = h2;
    h2 = t;
  }

  // One of the two cases has to hold in this case so we won't get to the
  // exception, but it'd be an exception if none of the cases were satisified
  // since this needs to return *something*.

  h1.right = (h1.right == null) ? h2 
             : (h1.right != null) ? mesh(h1.right, h2) 
             : throw new Exception();

  if(h1.left.rank < h1.right.rank)
  {
    // swap h1.left and h1.right

    HeapNode t = h1.left;
    h1.left = h1.right;
    h1.right = t;
  }

  h1.rank = h1.right.rank + 1;

  return h1;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.