まずはじめに
int a[10];
int *p;
p = a;
はOKだよね?式中で配列名を書くと(例外はあるが)その配列の先頭要素を指す
ポインタとして評価される。つまり
p = &(a[0]);
と同等。a[0]を囲む括弧は不要だけど優先順位を強調している。

最初の
char *pt[ ] = {"a", "b", "c", "d", "e" };
char **pp;
pp = pt;
はちょうどこの関係になっているのでOK。
ptの型は「charを指すポインタの配列」なのでptだけを書くとそれは
その配列の先頭要素を指すポインタとして評価される。
pt => &(pt[0])
このときのpt[0]の型は何かというとchar*。
&(char*)の型はchar**だから型があっているということです。

次にchar ***ppp = pp;自体は正当。これでppp,ppとも
同じ先を指すようになる。しかし型はchar***, char**で違う。
printf("%s ", ppp[i]);
の部分の「ppp[i」]の型に注意。pppはchar***なのでppp[i]の型は
char**になっている。たぶんpt, ppと同じような表示にさせたいなら
型をあわせなくてはならないので519さんのように
(*ppp)[i]とするか、(本質的には同じだけど)ppp[0][i]と書く。

余談だけど(*ppp)[i]と*ppp[i]の違いがわかっていないなら今のうちに
確認しておくといいよ。