文章目录
- 一.集合
- 1. 思维导图
- 2. List
- 2.1 ArrayList
- 2.2 Vector(数组实现,线程同步)
- 2.3 Linklist
- 3. Set
- 3.1 HashSet
- 3.2 TreeSet
- 3.3 LinkHashSet
- 4. Map
- 4.1 HashMap
- 4.2 ConcurrentHashMap
- 4.3 Hashtable
- 4.4 TreeMap
- LinkHashMap(记录插入顺序)
- 二.栈和队列
- 1 栈
- 1.1 栈的入栈操作
- 1.2 栈的出栈操作
- 1.3 如何用两个队列实现栈
- 2 队列
- 2.1 队列的入队操作
- 2.2 队列的出队操作
- 2.3 如何利用两个栈实现队列
- 三. 链表
- 1 链表的结构
- 2 链表插入
- 3 查找节点
- 4 移除节点
- 5 反转链表
- 5.1 三指针法
- 5.2 递归法
- 6 判断链表中是否有环
- 6.1 利用hashtable
- 6.2 快慢指针法
- 四.树
- 1 树的遍历
- 1.1 深度优先
- 1.1.1 先序
- 1.1.2 中序
- 1.1.3 后序
- 1.2 广度优先
- 2 手写一个二叉搜索树
- 2.1 树的结构
- 2.2 数的添加方法
- 2.3 树的删除
- 2.4 数的三种遍历(前序,中序和后序)
- 2.5 反转二叉树
一.集合
1. 思维导图
2. List
在java中List是一种非常常见的数据类型,List是一种有序的Collection,实现类有三种,分别是ArrayList,Vector,LinkedList。
2.1 ArrayList
ArrayList是最常用的list实现类,内部使用数组实现的,因为内部是用数组实现的,所以可以对元素快速的访问,但由于插入和移除需要对数组进行复制,移动,代价比较高。当数组大小不够的时候,就要将目前的数组复制到新的存储空间中,因此比较适合随机的查找和遍历,不适合插入和删除,并且是线程不安全的。
2.2 Vector(数组实现,线程同步)
Vector与Arraylist一样,底层都是由数组实现的,不同的是Vector是线程安全的,即某一时刻只有一个线程可以操作,但实现同步需要很高的花费,因此访问上来说比Arraylist慢。
2.3 Linklist
Linklist底层是使用链表实现的,很适合数据的删除与插入,随机访问和便利比较慢,。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
3. Set
Set注重独一无二的特性,该体系集合适用于存储无序,值不能重复的元素,对象是否相等是由hashcode值判断的,所以如果想让两个不同的对象视为相等的,就得重写Object的equlas和hashcode方法。
3.1 HashSet
哈希表里面存放的是哈希值,HashSet存储元素的顺序不是按照存储数据来排列的,而是按照哈希值来存的,所以取值也是按照哈希值来取的,元素的哈希值是通过元素hashcode方法来获取,HashSet 首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals 方法 如果 equls 结果为 true ,HashSet 就视为同一个元素。如果 equals 为 false 就不是同一个元素。哈希值相同 equals 为 false 的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。如图 1 表示 hashCode 值不相同的情况;图 2 表示 hashCode 值相同,但 equals 不相同的情况。
HashSet 通过 hashCode 值来确定元素在内存中的位置。一个 hashCode 位置上可以存放多个元素。
3.2 TreeSet
TreeSet是使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的,自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使用。
在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序
比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
3.3 LinkHashSet
对于LinkedHashSet而言,它继承于HashSet、又基于 LinkedHashMap 来实现的。LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承与 HashSet,其所有的方法操作上又与 HashSet 相同因此 LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个LinkedHashMap来实现,在相关操作上与父类HashSet的操作相同,直接调用父类 HashSet的方法即可。
4. Map
4.1 HashMap
HashMap根据键的hashcode值存储数据,可以通过键直接定位到它的值,所以具有很快的查询速度。但便利顺序是不确定的,HashMap最多只允许一条记录的值为null,允许多条记录的值为null,HashMap非线程安全,如果需要满足线程安全,可以用 Collections 的synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用ConcurrentHashMap。我们用下面这张图来介绍HashMap 的结构。
4.1.1 java7实现
大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个带颜色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。
- capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
- loadFactor:负载因子,默认为 0.75。
- threshold:扩容的阈值,等于 capacity * loadFactor
4.1.2 java8实现
Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。
4.2 ConcurrentHashMap
ConcurrentHashMap与hashmap差不多,但是ConcurrentHashMap支持多线程。整个ConcurrentHashMap是由多个Segment组成的,简单理解整个ConcurrentHashMap就是一个Segment数组,Segment通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
4.2.1 java中ConcurrentHashMap的结构
Java8 对 ConcurrentHashMap 进行了比较大的改动,Java8 也引入了红黑树。并且从ReentrantLock+Segment+HashEntry改成了synchronized+CAS+HashEntry+红黑树。
4.3 Hashtable
hashtable很多常用功能和hashmap一致,不同的是它承Dictionary类,并且它是线程安全的,任意时间只有一个线程能写hashtable,并发行不如ConcurrentHashMap好,因为ConcurrentHashMap加入了分段锁,所以在新代码中不建议用hashtable,在线程不安全的情况下hashmap更好,在线程安全的情况下又不如ConcurrentHashMap。
4.4 TreeMap
TreeMap实现了SortedMap接口,能够按照键进行排序,默认是按照键的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。如果使用排序的映射,建议使用 TreeMap。在使用TreeMap时,key必须实现Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException 类型的异常。
LinkHashMap(记录插入顺序)
LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。
二.栈和队列
1 栈
栈是一个先入后出的数据结构,栈可以用linklist或者数组来实现
1.1 栈的入栈操作
入栈就是往list的第一个地方插入元素
public void push(T e) {
list.addFirst(e);
// list.push(e);
}
1.2 栈的出栈操作
出栈就是从list的最后一个值取出
public T pop() {
//list.removeFirst();
return list.pop();
}
1.3 如何用两个队列实现栈
栈是先入后出结构,队列是先入先出结构,利用两个队列,可以入栈操作和出栈操作都在同一个队列中实现,然后另外一个队列只是一个中转站,每次入栈都是存入队列1中,而出栈则是把队列1中除最后一个元素外都放到队列2中,在出栈队列1中剩余的那个元素,在把队列2中的元素放回到队列1中。
入栈
public void push(int item) throws InterruptedException {
queue1.put(item);
}
出栈
public Object pop() throws InterruptedException {
while (queue1.size()!=1){
queue2.put(queue1.remove());
}
int res = queue1.remove();
while (queue2.size()!=0){
queue1.put(queue2.remove());
}
return res;
}
2 队列
队列是一个先入先出的数据结构,队列可以用linklist或者数组来实现
2.1 队列的入队操作
入队就是往list中添加元素
public void enqueue(T e){
list.add(e);
}
2.2 队列的出队操作
出队就是从list中取出元素
public T dequeue(){
return list.remove();
}
2.3 如何利用两个栈实现队列
栈是先入后出结构,那么从第一个栈入,然后把数据导入到第二个栈中,然后从第二个栈中出,就实现了队列
入栈
public void enqueue(T e) {
s1.push(e);
}
出栈,先判断栈2中是否有元素,如果有那么就直接从栈2中取值,如果没有,需要先把栈1中的数据同步到栈2中
public T dequeue() {
if(s2.size() > 0) {
return s2.pop();
}
while(s1.size() > 0) {
s2.push(s1.pop());
}
return s2.pop();
}
三. 链表
1 链表的结构
一个链表节点对象需要有当前节点的值,和当前节点的下一个节点的指针两个信息,如果是双向链表还需要一个上一个节点的指针信息
static class Node<T> {
Node<T> next = null;
T data;
public Node(T data){
this.data = data;
}
}
2 链表插入
利用头插法,因为尾插法对于单向链表来说要遍历到最后一个节点,时间复杂度比较高,
插入节点的下一个节点 -> 头结点
头结点 -> 插入节点
public void insert(T data) {
var node = new Node<>(data);
node.next = head;
head = node;
}
3 查找节点
遍历整个链表,直到找到节点
public Node<T> find(Predicate<T> predicate) {
var p = head;
while(p != null) {
if(predicate.test(p.data)) {
return p;
}
p = p.next;
}
return null;
}
4 移除节点
利用双指针,一个指向当前节点,一个指向当前节点的下一个节点,移除节点就是把移除节点的前一个节点 -> 移除节点的后一个节点
public void remove(Node<T> node){
if(head == null) {
return;
}
if(head == node) {
head = head.next;
return;
}
var slow = head;
var fast = head.next;
while(fast != node && fast != null) {
slow = fast;
fast = fast.next;
}
if(fast != null) {
slow.next = fast.next;
// fast.data = null;
}
}
5 反转链表
5.1 三指针法
用三个指针,一个为当前节点,一个是当前节点的上一个节点,一个是当前节点的下一个节点。每次换的时候就是
当前节点的下一个节点 -> 当前节点的上一个节点
把当前节点的上一个节点 -> 当前节点
当前节点 -> 当前节点的下一个节点
public void reverse(){
// prev | current | next
Node<T> prev = null;
var current = head;
Node<T> next;
while(current != null) {
next = current.next;
current.next = prev;
prev = current;
current = next;
}
head = prev;
}
5.2 递归法
可以把整个链表看成是头结点和其他节点的关系,每次递归的意味着其他节点一个都反转完了,只剩下头结点和其他节点了,那么每次就把头结点给处理好就行了。
那么就需要把
头结点的下一个节点的下一个节点止回自己就可以了。
然后把头结点指向空
private Node<T> _reverse2(Node<T> head) {
if(head == null || head.next == null) {
return head;
}
var rest = _reverse2(head.next);
head.next.next = head;
head.next = null;
return rest;
}
public void reverse2() {
head = _reverse2(head);
}
6 判断链表中是否有环
6.1 利用hashtable
每次把链表节点存入到hashtable中,如果要是有环,那么就会一样的节点
public boolean hasLoop1(){
var set = new HashSet<>();
var p = head;
while(p != null) {
if(set.contains(p)) {
return true;
}
set.add(p);
p = p.next;
}
return false;
}
6.2 快慢指针法
利用快慢指针,一个每次前进一个,一个每次前进两个,如果有环,那么总会有两个指针指向同一个节点的情况
public boolean hasLoop2(){
if(head == null || head.next == null) {
return false;
}
var slow = head;
var fast = head.next.next;
while(fast != null && fast.next != null) {
if(fast == slow) {return true;}
slow = slow.next;
fast = fast.next.next;
}
return false;
}
全代码
package datastructure;
import org.junit.Test;
import java.util.HashMap;
import java.util.HashSet;
import java.util.function.Predicate;
import static org.junit.Assert.assertEquals;
public class List<T> {
static class Node<T> {
Node<T> next = null;
T data;
public Node(T data){
this.data = data;
}
}
Node<T> head = null;
// O(1)
public void insert(T data) {
var node = new Node<>(data);
node.next = head;
head = node;
}
// O(n)
public Node<T> find(Predicate<T> predicate) {
var p = head;
while(p != null) {
if(predicate.test(p.data)) {
return p;
}
p = p.next;
}
return null;
}
public Integer size(){
var p = head;
Integer c = 0;
while(p != null) { p = p.next; c++; }
return c;
}
// O(n)
public void remove(Node<T> node){
if(head == null) {
return;
}
if(head == node) {
head = head.next;
return;
}
var slow = head;
var fast = head.next;
while(fast != node && fast != null) {
slow = fast;
fast = fast.next;
}
if(fast != null) {
slow.next = fast.next;
// fast.data = null;
}
}
public void reverse(){
// prev | current | next
Node<T> prev = null;
var current = head;
Node<T> next;
while(current != null) {
next = current.next;
current.next = prev;
prev = current;
current = next;
}
head = prev;
}
private Node<T> _reverse2(Node<T> head) {
if(head == null || head.next == null) {
return head;
}
var rest = _reverse2(head.next);
head.next.next = head;
head.next = null;
return rest;
}
public void reverse2() {
head = _reverse2(head);
}
public boolean hasLoop1(){
var set = new HashSet<>();
var p = head;
while(p != null) {
if(set.contains(p)) {
return true;
}
set.add(p);
p = p.next;
}
return false;
}
public boolean hasLoop2(){
if(head == null || head.next == null) {
return false;
}
var slow = head;
var fast = head.next.next;
while(fast != null && fast.next != null) {
if(fast == slow) {return true;}
slow = slow.next;
fast = fast.next.next;
}
return false;
}
@Test
public void test_insert(){
var list = new List<Integer>();
list.insert(1);
list.insert(2);
list.insert(3);
var p = list.head;
for(Integer i = 3; p != null ; i--) {
assertEquals(i, p.data);
p = p.next;
}
}
@Test
public void test_find_remove(){
var list = new List<String>();
list.insert("C++");
list.insert("Java");
list.insert("C");
list.insert("C#");
list.insert("python");
var node = list.find(x -> x == "Java");
assertEquals("Java", node.data);
var node2 = list.find(x -> x == "ruby");
assertEquals(null, node2);
list.remove(node);
assertEquals(Integer.valueOf(4), list.size());
assertEquals(null, list.find(x -> x == "Java"));
}
@Test
public void test_reverse() {
var list = new List<Integer>();
list.insert(1);
list.insert(2);
list.insert(3);
list.reverse();
var p = list.head;
for (Integer i = 1; p != null; i++) {
assertEquals(i, p.data);
p = p.next;
}
}
@Test
public void test_reverse2() {
var list = new List<Integer>();
list.insert(1);
list.insert(2);
list.insert(3);
list.reverse2();
var p = list.head;
for (Integer i = 1; p != null; i++) {
assertEquals(i, p.data);
p = p.next;
}
}
@Test
public void test_loop(){
var list = new List<Integer>();
list.insert(3);
list.insert(2);
list.insert(1);
list.insert(0);
var node = list.find(x -> x == 3);
node.next = list.head;
assertEquals(true, list.hasLoop1());
assertEquals(true, list.hasLoop2());
}
}
四.树
1 树的遍历
1.1 深度优先
1.1.1 先序
1.1.2 中序
1.1.3 后序
1.2 广度优先
2 手写一个二叉搜索树
二叉搜索树是一个中序遍历的数,它的特点就是左子树节点比当前节点小,右子树节点比当前节点大
2.1 树的结构
里面包含左子点的指针和右子点的指针,还有一个自己的数据
static class BSTNode<T> {
BSTNode<T> left = null;
BSTNode<T> right = null;
T data;
public BSTNode(T data) {
this.data = data;
}
}
2.2 数的添加方法
利用递归去添加首先先判断添加的节点与当前节点的大小,如果大往右边添加,反之往左添加,然后看看右节点是否有值,如果有那么迭代比较,如果没有值那么就添加到这,左边同理
private void add(BSTNode<T> node, BSTNode<T> element) {
if(element.data.compareTo(node.data) <= 0) {
if(node.left == null) {
node.left = element;
return;
}
add(node.left, element);
} else {
if(node.right == null) {
node.right = element;
return;
}
add(node.right, element);
}
}
public void add(T element) {
var node = new BSTNode<>(element);
if(root == null) {
root = node;
return;
}
add(root, node);
}
2.3 树的删除
数的删除需要考虑的点有很多
- 删除的节点没有左右节点,比较容易,直接删掉就可以
- 删除的节点有一个子节点,那么就需要把子节点指向是当前节点的上一个节点
- 删除的节点左右节点都有,需要挑选左子树中最大的或者右子树中最小的,替换当前节点,再将替换的节点置空
public BSTNode<T> delete(BSTNode<T> root,BSTNode<T> val){
if(root == null){
return root;
}
if(root.data.compareTo(val.data)<0){
root.right = delete(root.right,val);
return root;
}
if(root.data.compareTo(val.data)>0){
root.left = delete(root.left,val);
return root;
}
if(root.left==null&&root.right==null){
root = null;
return root;
}
if(root.left==null&&root.right!=null){
root = root.right;
return root;
}
if(root.right==null&&root.left!=null){
root = root.left;
return root;
}
if(root.left!=null&&root.right!=null){
T a = findMaxInLeftTree(root.left);
root.data = a;
root.left = delete(root.left,val);
return root;
}
return root;
}
//找到左子树中最大的值
private T findMaxInLeftTree(BSTNode<T> left) {
if(left == null){
return null;
}
if(left.right == null){
return left.data;
}
if(left.right == null && left.left == null){
return left.data;
}
return findMaxInLeftTree(left.right);
}
2.4 数的三种遍历(前序,中序和后序)
其实这个很简单,如果是前序那么就先打印数据,然后再左右节点递归,如果是后序就先左右节点递归在打印,如果是中序,那么就先左节点然后打印然后在右节点
<T> void preOrder(BSTNode<T> node) {
if(node == null) {
return;
}
System.out.println(node.data);
preOrder(node.left);
preOrder(node.right);
}
<T> void postOrder(BSTNode<T> node){
if(node == null) {
return;
}
postOrder(node.left);
postOrder(node.right);
System.out.println(node.data);
}
<T> void inOrder(BSTNode<T> node){
if(node == null) {
return;
}
inOrder(node.left);
System.out.println(node.data);
inOrder(node.right);
}
2.5 反转二叉树
递归的来看就是把当前层的左右节点互换,然后再去考虑下一层
public static <T> void reverse(BSTNode<T> node) {
if(node == null) {
return;
}
var t = node.left;
node.left = node.right;
node.right = t;
reverse(node.left);
reverse(node.right);
}
全部源代码
package datastructure;
import org.junit.Test;
public class BSTree<T extends Comparable<T>> {
BSTNode<T> root = null;
static class BSTNode<T> {
BSTNode<T> left = null;
BSTNode<T> right = null;
T data;
public BSTNode(T data) {
this.data = data;
}
}
private void add(BSTNode<T> node, BSTNode<T> element) {
if(element.data.compareTo(node.data) <= 0) {
if(node.left == null) {
node.left = element;
return;
}
add(node.left, element);
} else {
if(node.right == null) {
node.right = element;
return;
}
add(node.right, element);
}
}
public void add(T element) {
var node = new BSTNode<>(element);
if(root == null) {
root = node;
return;
}
add(root, node);
}
<T> void preOrder(BSTNode<T> node) {
if(node == null) {
return;
}
System.out.println(node.data);
preOrder(node.left);
preOrder(node.right);
}
<T> void postOrder(BSTNode<T> node){
if(node == null) {
return;
}
postOrder(node.left);
postOrder(node.right);
System.out.println(node.data);
}
<T> void inOrder(BSTNode<T> node){
if(node == null) {
return;
}
inOrder(node.left);
System.out.println(node.data);
inOrder(node.right);
}
// Breadth First Search
public static <T> void bfs(BSTNode<T> node){
var queue = new Queue<BSTNode<T>>();
queue.enqueue(node);
while(queue.size() > 0) {
var item = queue.dequeue();
System.out.println(item.data);
if(item.left != null)
queue.enqueue(item.left);
if(item.right != null)
queue.enqueue(item.right);
}
}
public static <T> void reverse(BSTNode<T> node) {
if(node == null) {
return;
}
var t = node.left;
node.left = node.right;
node.right = t;
reverse(node.left);
reverse(node.right);
}
public BSTNode<T> delete(BSTNode<T> root,BSTNode<T> val){
if(root == null){
return root;
}
if(root.data.compareTo(val.data)<0){
root.right = delete(root.right,val);
return root;
}
if(root.data.compareTo(val.data)>0){
root.left = delete(root.left,val);
return root;
}
if(root.left==null&&root.right==null){
root = null;
return root;
}
if(root.left==null&&root.right!=null){
root = root.right;
return root;
}
if(root.right==null&&root.left!=null){
root = root.left;
return root;
}
if(root.left!=null&&root.right!=null){
T a = findMaxInLeftTree(root.left);
root.data = a;
root.left = delete(root.left,val);
return root;
}
return root;
}
//找到左子树中最大的值
private T findMaxInLeftTree(BSTNode<T> left) {
if(left == null){
return null;
}
if(left.right == null){
return left.data;
}
if(left.right == null && left.left == null){
return left.data;
}
return findMaxInLeftTree(left.right);
}
@Test
public void test(){
System.out.println("abcdefghijklmn".hashCode());
var o = new Object();
o.hashCode();
BSTree tree = new BSTree<Integer>();
tree.add(10);
tree.add(7);
tree.add(5);
tree.add(8);
tree.add(15);
tree.add(30);
tree.add(21);
var printer = new TreePrinter();
printer.print(tree.root);
var node = new BSTNode<>(15);
delete(tree.root, (BSTNode<T>) node);
printer.print(tree.root);
//bfs(tree.root);
//preOrder(tree.root);
//postOrder(tree.root);
// inOrder(tree.root);
}
@Test
public void test_reverse(){
var tree = new BSTree<Integer>();
tree.add(10);
tree.add(7);
tree.add(5);
tree.add(8);
tree.add(15);
tree.add(30);
tree.add(21);
var printer = new TreePrinter();
printer.print(tree.root);
tree.reverse(tree.root);
printer.print(tree.root);
}
}
打印二叉树源代码
package datastructure;
import java.util.ArrayList;
public class TreePrinter {
<T> int heightOf(BSTree.BSTNode<T> node) {
if(node == null) {
return 0;
}
return Math.max(
heightOf(node.left),
heightOf(node.right)
) + 1;
}
public <T> void print(BSTree.BSTNode<T> root) {
int h = heightOf(root);
int W = 2*(int)Math.pow(2, h);
var lines = new StringBuilder[h*2];
for(int i = 0; i < h*2; i++) {
lines[i] = new StringBuilder(String.format("%" + W + "s", ""));
}
printNode(lines, W, root, 0, 0);
for(var line : lines) {
System.out.println(line.toString());
}
}
private <T> void printNode(StringBuilder[] lines, int W, BSTree.BSTNode<T> node, int h, int base) {
var nums = Math.pow(2, h);
var pos = base + (int)(W / (nums * 2));
var str = node.data.toString();
for(int i = 0; i < str.length(); i++) {
lines[h*2].setCharAt(pos + i, str.charAt(i));
}
if(node.left != null) {
lines[h*2+1].setCharAt(pos-1, '/');
printNode(lines, W, node.left, h+1, base);
}
if(node.right != null) {
lines[h*2 + 1].setCharAt(pos + str.length() + 1, '\\');
printNode(lines, W, node.right, h+1, pos);
}
}
}