C++ syntax/debug question with binary recursive functions - c++

I have been scratching my head for a while on this C++ program debug. I'm trying to write a program to help validate if the binary search tree syntax is correct by construction. I'm not a lost for why my program seems to be crashing at the recursive part of the program.
I have a simple Node class:
#include <iostream>
class Node
{
public:
Node(int value, Node* left, Node* right)
{
this->value = value;
this->left = left;
this->right = right;
}
int getValue() const
{
return value;
}
Node* getLeft() const
{
return left;
}
Node* getRight() const
{
return right;
}
private:
int value;
Node* left;
Node* right;
};
Here is the problematic function:
class BinarySearchTree
{
public:
static bool isValidBST(const Node* node) {
std::cout << "DEBUG 1\n";
/* false if left is > than node */
if (node->getLeft() != NULL && node->getLeft()->getValue() > node->getValue())
return false;
std::cout << "DEBUG 2\n";
/* false if right is < than node */
if (node->getRight() != NULL && node->getRight()->getValue() < node->getValue())
return false;
std::cout << "DEBUG 3\n";
/* false if, recursively, the left or right is not a BST */
//std::cout << isValidBST(node->getLeft()) << std::endl;
if (!isValidBST(node->getLeft()) || !isValidBST(node->getRight()))
return false;
/* passing all that, it's a BST */
return true;
}
};
It's called like this:
int main()
{
Node n1(1, NULL, NULL);
Node n3(3, NULL, NULL);
Node n2(2, &n1, &n3);
std::cout << " TEST ";
std::cout << BinarySearchTree::isValidBST(&n2);
std::cout << " DONE " ;
return 0;
}

Your program suffers from too much repetition, and some assumptions about pointers. To begin with, if you call isValidBST(NULL) your program exhibits undefined behavior even though one expects that an empty tree is a valid BST.
Beyond this, your compound conditional statements are duplicating not only pointer test logic, but also the comparisons which it turns out you also did not get correct. You have not considered that duplicate values in a BST are also invalid, and so fixing this logic requires multiple edits.
In general, maintaining identical logic in multiple parts of a program (or a function) adds the potential for future bugs, and it's good to avoid when possible. This is especially true when the duplicated logic is inverted.
Consider the following rearrangement of your function, with the logic in one place:
class BinarySearchTree
{
public:
static bool isValidBST(const Node* node)
{
const Node* prev = NULL;
return isValidBST(node, prev);
}
private:
static bool isValidBST(const Node* node, const Node* &prev)
{
if (!node)
return true;
// Validate left
if (!isValidBST(node->getLeft(), prev))
return false;
// Validate current
if (prev && prev->getValue() >= node->getValue())
return false;
prev = node;
// Validate right
return isValidBST(node->getRight(), prev);
}
};
The function bootstraps a private overload that maintains a pointer of the last value node tested. In this way, you can see the code is vastly simplified and more obvious to the reader.

You need to check that in isValidBST method your node variable is null or not. if node is null and you call
node->getLeft(), node->getRight() or node->getValue()
you will get an error. You need to add null check conditon in isValidBST method
if(node == NULL) return true;
Your can resolve this like
#include <stdexcept>
#include <string>
#include <iostream>
#include <algorithm>
class Node
{
public:
Node(int value, Node* left, Node* right)
{
this->value = value;
this->left = left;
this->right = right;
}
int getValue() const
{
return value;
}
Node* getLeft() const
{
return left;
}
Node* getRight() const
{
return right;
}
private:
int value;
Node* left;
Node* right;
};
class BinarySearchTree
{
public:
static bool isValidBST(const Node* node) {
if(node == NULL) return true;
std::cout << "DEBUG 1\n";
/* false if left is > than node */
if (node->getLeft() != NULL && node->getLeft()->getValue() > node->getValue())
return false;
std::cout << "DEBUG 2\n";
/* false if right is < than node */
if (node->getRight() != NULL && node->getRight()->getValue() < node->getValue())
return false;
std::cout << "DEBUG 3\n";
/* false if, recursively, the left or right is not a BST */
//std::cout << isValidBST(node->getLeft()) << std::endl;
if (!isValidBST(node->getLeft()) || !isValidBST(node->getRight()))
return false;
/* passing all that, it's a BST */
return true;
}
};
#ifndef RunTests
int main()
{
Node n1(1, NULL, NULL);
Node n3(3, NULL, NULL);
Node n2(2, &n1, &n3);
std::cout << " TEST ";
std::cout << BinarySearchTree::isValidBST(&n2);
std::cout << " DONE " ;
return 0;
}
#endif```

I found a logic bug with my recursive program:
needed this extra case with the NULL left and NULL right node.
class BinarySearchTree
{
public:
static bool isValidBST(const Node* node) {
std::cout << "DEBUG 1\n";
/* false if left is > than node */
if (node->getLeft() != NULL && node->getLeft()->getValue() > node->getValue())
return false;
std::cout << "DEBUG 2\n";
/* false if right is < than node */
**if (node->getRight() != NULL && node->getRight()->getValue() < node->getValue())
return false;**
std::cout << "DEBUG 3\n";
if (node->getLeft() == NULL && node->getRight() == NULL) {
return true;
}
/* false if, recursively, the left or right is not a BST */
//std::cout << isValidBST(node->getLeft()) << std::endl;
if (!isValidBST(node->getLeft()) || !isValidBST(node->getRight()))
return false;
/* passing all that, it's a BST */
return true;
}
};```

Related

Find if the binary search tree has duplicated value

For binary search tree to see if the tree has duplicated value or not. I took this post order approach.
My goal was to keep the value of the current node and then use other function traverse the tree to see if there is any matching value to that current value, and if it finds any duplicate value it brings "true value". I choose to use recursion as it seems easier to track. but when I ran the program there was no output coming out.
#include "pch.h"
#include <iostream>
using namespace std;
class BSTNode {
public:
int data;
BSTNode* left;
BSTNode* right;
BSTNode() {};
};
BSTNode* newnode(int newdata) { BSTNode *curr = new BSTNode; curr->data = newdata; curr->left = curr->right = nullptr; return curr; }
void print(BSTNode* root) {
if (root != nullptr) {
print(root->left);
cout << root->data << endl;
print(root->right);
}
}
bool checking(BSTNode* parent, int val) {
if (val == parent->data){
bool left = checking(parent->left, val);
bool right = checking(parent->right, val);
return left||right;
}
else
return false;
}
bool assist(BSTNode* parent) {
if (parent != nullptr) {
assist(parent->left);
assist(parent->right);
return checking(parent, parent->data);
}
else return false;
}
int main() {
BSTNode *test = newnode(1);
test->left=newnode(2);
test->right=newnode(3);
test->left->left=newnode(2);
test->right->right=newnode(5);
print(test);
if (assist(test))
cout << "There is duplicated" << endl;
else
cout << "There is no duplicated" << endl;
return 0;
}
Your checking function should look like this:
bool checking(BSTNode* parent, int val) {
if(parent == nullptr) // point 1
return false;
if (val == parent->data){ // point 2
return true;
}
else{
bool left = checking(parent->left, val);
bool right = checking(parent->right, val);
return left||right;
}
}
Your assist function should look something like this:
bool assist(BSTNode* parent) {
if (parent != nullptr) {
if(checking(parent->left, parent->data)) return true; // point 3
if(checking(parent->right, parent->data)) return true;
return assist(parent->left)||assist(parent->right); // point 4
}
else return false;
}
You need to check for null values.
If val is same, why are you still checking? Just stop
You need to check node's value in the left and right subtree.
Recurse it for the child nodes
If you want to check that parent value is different than child values, you might do:
bool checking(const BSTNode* node, int parent_value) {
if (node == nullptr) { return false; }
if (node->data == parent_value) { return true; }
return checking(node->left, node->data)
|| checking(node->right, node->data);
}
bool assist(const BSTNode* parent) {
if (parent == nullptr) {
return false;
}
return checking(parent->left, parent->data)
|| checking(parent->right, parent->data);
}
You could just go through the BST breadth wise with a Deque. Store the values in a set and check if the value is already in the set, if it is return true otherwise wait for the loop to finish and return true. This had the benefit of hash table lookup for values at thr cost of extra storage in O(n) time. Its also easier to follow in my opinion as it's not recursion.
bool hasDuplicate(BSTNode *parent)
{
if (!parent) return false;
std::dueue<BSTNode*> nodes;
std::unordered_set<int> vals;
nodes.push_back(parent);
while(!nodes.empty()) {
BSTNode *node = nodes.pop_front();
int v = nodes->val;
// Check if value exists and return true
if(vals.find(v) != vals.end()) return true;
// Otherwise insert it
vals.insert(v);
// insert left node if exists
if (node->left) nodes.push_back(node->left);
// insert right node if exists
if (node->right) nodes.push_back(node->right);
}
// no dups found
return false;
}
Sorry for bad indents. Did this on phone lol.

BST Node Deletion - Pointer not being deleted correctly?

I'm running some tests on a simple 3 node binary search tree. Root node has a value of 1 and its left and right children have values of 0 and 2, respectively.
Here's the source code (3 files):
File name: bst.cpp
#include <iostream>
#include "bst.h"
template <typename T>
void binary_search_tree<T>::insert(const T val2ins)
{
num_nodes++;
if(!root)
{
root = new tree_node<T>(val2ins, nullptr, nullptr, nullptr);
return;
}
//loop from root until we find where to insert val2ins; seek to find a suitable parent with a nullptr
auto curr_node = root;
tree_node<T> *prev_node = nullptr;
while(curr_node)
{
prev_node = curr_node;
if(val2ins >= curr_node->val) //assign equalities on right children
{
curr_node = curr_node->right;
}
else
{
curr_node = curr_node->left;
}
}
//prev_node is the parent of curr_node
curr_node = new tree_node<T>(val2ins, prev_node, nullptr, nullptr);
//curr_node now points to a tree_node that contains a pointer to to the previous node
//we also need to go to previous_node and set its left/right children to curr_node
if(curr_node->val < prev_node->val)
{
prev_node->left = curr_node;
}
else
{
prev_node->right = curr_node;
}
}
template <typename T>
tree_node<T> *binary_search_tree<T>::get_root()
{
return root;
}
File name: bst.h
#ifndef _BST_H_
#define _BST_H_
template<typename T>
struct tree_node
{
T val;
tree_node *parent;
tree_node *left;
tree_node *right;
tree_node() : val(0), parent(nullptr), left(nullptr), right(nullptr) {}
tree_node(T val2ins, tree_node *p_ptr, tree_node *l_ptr, tree_node *r_ptr)
{
val = val2ins;
parent = p_ptr;
left = l_ptr;
right = r_ptr;
}
};
template<typename T>
class binary_search_tree
{
private:
int num_nodes;
tree_node<T> *root;
//helper function for deletion
void transplant(const tree_node<T> *node2replace, const tree_node<T> *node2insert);
public:
binary_search_tree() : num_nodes(0), root(nullptr) {}
binary_search_tree(int N, tree_node<T> *ptr) : num_nodes(N), root(ptr) {}
void insert(const T val2ins);
void delete_node(const tree_node<T> *node2del);
tree_node<T> *get_root();
// void
};
#endif
File name: main.cpp
#include <iostream>
#include "bst.h"
#include "bst.cpp"
template <typename T>
class Solution {
public:
tree_node<T> *trimBST(tree_node<T> *root, int L, int R) {
search_and_delete(root, L, R);
return root;
}
void search_and_delete(tree_node<T> *&node, const int L, const int R)
{
if(!node)
{
return;
}
if(node && node->val >= L && node->val <= R)
{
trimBST(node->right, L, R);
std::cout << node->left << std::endl;
trimBST(node->left, L, R);
std::cout << node->left << std::endl;
std::cout << node->left << std::endl;
}
else if(node && node->val > R)
{
//delete right sub tree
//then check left sub tree
//Also need to delete current node and link left (if needed)
//this can be done by simply setting current node to its left
if(node->left == nullptr && node->right == nullptr)
{
delete node;
node = nullptr;
return;
}
if(node->right)
{
delete node->right;
node->right = nullptr;
}
if(node->left)
{
node = node->left;
}
}
else if(node && node->val < L)
{
//delete left sub tree
//then check right sub tree
//Also need to delete current node and link right (if needed)
//this can be done by simply setting current node to
//its right
if(node->left == nullptr && node->right == nullptr)
{
std::cout << "deleting node 0" << std::endl;
std::cout << "Address prior to freeing: " << node << std::endl;
delete node;
node = nullptr;
std::cout << "Address after freeing: " << node << std::endl;
return;
}
if(node->left)
{
delete node->left;
node->left = nullptr;
}
if(node->right)
{
node = node->right;
}
std::cout << "done" << std::endl;
}
std::cout << "end" << std::endl;
}
};
int main(int argc, char const *argv[])
{
/* code */
binary_search_tree<int> my_tree;
Solution<int> soln;
my_tree.insert(1);
my_tree.insert(0);
my_tree.insert(2);
soln.trimBST(my_tree.get_root(), 1, 2);
return 0;
}
When I execute this code I get the following output:
0x0
0x0
0x0
end
0x7fdeaec02af0
deleting node 0
Address prior to freeing: 0x7fdeaec02af0
Address after freeing: 0x0
0x7fdeaec02af0
0x7fdeaec02af0
end
The pointer pointing to the node with value 0 is being deleted during the recursive call and set to nullptr. However, when it returns from the recursive call (where the pointer was passed by reference), the pointer is still pointing to the same memory address as it did prior to being deleted and set to nullptr.
I cannot figure out why this is happening. My only guess is that I have a memory leak somewhere that's causing issues with the pointer that I supposedly applied delete to.
At first let me to say you that your node struct just should have one content and two pointer of himself that will show the right and left children.
then for showing the BST you should cout the data NOT this pointers
class node
{
friend class BST;
public:
node(int Content=0,node* R_child = NULL, node*L_child = NULL)
{
this->R_child = R_child;
this->L_child = L_child;
this->Content = Content;
}
private:
int Content;
node*R_child;
node*L_child;
};
check this code for node class you can use template instead of integer.
Good Luck

Binary search tree from testdome

I am writing the the answer for a test sample given in testdome https://www.testdome.com/for-developers/solve-question/9708
The question is about binary search tree:
Binary search tree (BST) is a binary tree where the value of each node is larger or equal to the values in all the nodes in that node's left subtree and is smaller than the values in all the nodes in that node's right subtree.
Write a function that checks if a given binary search tree contains a given value.
For example, for the following tree:
n1 (Value: 1, Left: null, Right: null)
n2 (Value: 2, Left: n1, Right: n3)
n3 (Value: 3, Left: null, Right: null)
Call to contains(n2, 3) should return true since a tree with root at n2 contains number 3.
I modified the code as below, the output looks working well, but the test result tells one fail exist as: Performance test on a large tree: Time limit exceeded
Can you help to modified my mode to fix this fail?
#include <stdexcept>
#include <string>
#include <iostream>
class Node
{
public:
Node(int value, Node* left, Node* right)
{
this->value = value;
this->left = left;
this->right = right;
}
int getValue() const
{
return value;
}
Node* getLeft() const
{
return left;
}
Node* getRight() const
{
return right;
}
private:
int value;
Node* left;
Node* right;
};
class BinarySearchTree
{
public:
static bool contains(const Node& root, int value)
{
Node* tree;
int val = root.getValue();
std::cout<<"current node's value is:"<<val<<'\n';
if (val==value)
{
return true;
}
else if (val>value)
{
tree = root.getLeft();
if(tree != NULL)
{
std::cout<<"left node's value is:"<<tree->getValue()<<'\n';
return contains(*tree, value);
}
else
{
return false;
}
}
else
{
tree = root.getRight();
if(tree != NULL)
{
std::cout<<"right node's value is:"<<tree->getValue()<<'\n';
return contains(*tree, value);
}
else
{
return false;
}
}
//throw std::logic_error("Waiting to be implemented");
}
};
#ifndef RunTests
int main()
{
Node n1(1, NULL, NULL);
Node n3(3, NULL, NULL);
Node n2(2, &n1, &n3);
std::cout << BinarySearchTree::contains(n2, 3);
}
#endif
Remove std::cout will do. Printing to terminal has a huge time cost.
Oh, A better solution here. Why do you want to use temporary variables? While using recursion remember that the temporary variables do get stored on function's calling stack also don't use print statements.
static bool contains(const Node& root, int value)
{
if(root.getValue() == value){
return true;
}
else if(root.getValue() < value && root.getRight() != NULL){
return contains(*(root.getRight()), value);
}
else if(root.getLeft() != NULL){
return contains(*(root.getLeft()), value);
}
return false;
}

How to implement insert function, with no pointer node as a parameter, in binary tree?

tree.h file that implements all the methods. The insert function is in this file. Everything I try requires a the use of a tree pointer node to be passed as a parameter but we cant change the method headers at all for this assignment so Im lost as to how to implement it. Any help is greatly appreciated thanks!!
#include "treeNode.h"
#include <iomanip>
template <class V>
class tree {
TreeNode<V> * root;
int size;
public:
// default constructor
// by default, the tree is empty
tree(){
root = nullptr;
size = 0;
}
// search value x in tree rooted at node t
bool treeSearch(V x, TreeNode<V>* t){
if(t == nullptr) return false;
if(t->getDatum() == x) return true;
return treeSearch(x, t->getLeft()) || treeSearch(x, t->getRight());
}
bool treeSearch(V x){
treeSearch(x, root);
}
// binary search value x in tree rooted at node t
bool treeBSearch(V x, TreeNode<V>* t){
if(t == nullptr) return false;
if(t->getDatum() == x) return true;
if(t->getDatum() > x)
return treeBSearch(x, t->getLeft());
else
return treeBSearch(x, t->getRight());
}
bool treeBSearch(V x){
return treeBSearch(x, root);
}
// check node t is leaf
bool isLeaf(TreeNode<V>* t){
if(t != nullptr && t->getLeft() == nullptr && t->getRight() == nullptr){
return true;
}else{
return false;
}
}
// find the height of the tree rooted at node t
int height(TreeNode<V>* t){
if(t == nullptr) return -1;
if(isLeaf(t)) return 0;
return 1 + std :: max(height(t->getLeft()), height(t->getRight()));
}
int height(){
return height(root);
}
// find the number of nodes of tree rooted at t
int nNodes(TreeNode<V>* t){
if(t == nullptr) return 0;
return 1 + nNodes(t->getLeft()) + nNodes(t->getRight());
}
int nNodes(){
return nNodes(root);
}
// insert value x to the current tree object
void insert(V x){
}
// print out the values of tree rooted at x
// it shows the hierarchy of the tree
// it will be useful for debugging
void print(TreeNode<V> * x, int indent){
if(x == nullptr) return;
if (x->getRight() != nullptr) {
print(x->getRight(), indent+4);
}
if (indent != 0) {
cout << std::setw(indent) << ' ';
}
if(x->getRight() != nullptr){
cout << " /\n" << std::setw(indent) << ' ';
}
cout << x->getDatum() << endl;
if (x->getLeft() != nullptr) {
cout << std::setw(indent) << ' ' <<" \\\n";
print(x->getLeft(), indent+4);
}
}
void print(){
int count = 0;
print(root, count);
}
};
treeNode.h file that has declares methods
#include
#include
#include
using namespace std;
template <class T>
class TreeNode{
T datum;
TreeNode<T>* left, * right;
public:
// constructor with datum value, left and right are nullptr
TreeNode(T x){
datum=x;
left = nullptr;
right = nullptr;
}
// constructor with datum value, left and right values
TreeNode(T x, TreeNode<T>* lft, TreeNode<T>* rgt){
datum = x;
left = lft;
right = rgt;
}
//destructor releases left and right nodes, if not nullptr
~TreeNode(){
if (left) {
delete left;
}
if (right) {
delete right;
}
}
// get datum value
T getDatum(){
return datum;
}
// get left pointer
TreeNode<T>* getLeft(){
return left;
}
// get right pointer
TreeNode<T>* getRight(){
return right;
}
// set the left pointer
void setLeft(TreeNode<T>* p){
left = p;
}
// set the right pointer
void setRight(TreeNode<T>* p){
right = p;
}
};
test.cpp file that tests whether or not the methods work
#include "tree.h"
int main(){
tree<int> myTree;
myTree.insert(5);
myTree.insert(3);
myTree.insert(2);
myTree.insert(4);
myTree.insert(8);
myTree.insert(6);
myTree.insert(7);
myTree.insert(9);
cout << myTree.treeBSearch(9) << endl;
cout << myTree.treeBSearch(11) << endl;
cout << myTree.nNodes() << endl;
cout << "The Tree Looks Like: " << endl;
myTree.print();
tree<int> myTree2;
myTree2.insert(4);
myTree2.insert(2);
myTree2.insert(1);
myTree2.insert(3);
myTree2.insert(7);
myTree2.insert(5);
// myTree2.print();
}

Why am I receiving segmentation fault in my insert function of a tree?

The segmentation fault occurs in the insert function specifically at the *root = x; in the first if statement. The functions were already laid out and we just had to implement them so I cant add another parameter or anything. Ive been stuck on this for a while and have no idea what it could be. The root pointer was allocated memory so I really dont know what the problem is. Any help is appreciated, thanks.
#include "treeNode.h"
#include <iomanip>
template <class V>
class tree {
TreeNode<V> * root;
TreeNode<V> * cur;
int size;
public:
// default constructor
// by default, the tree is empty
tree(){
root = nullptr;
size = 0;
}
// search value x in tree rooted at node t
bool treeSearch(V x, TreeNode<V>* t){
if(t == nullptr) return false;
if(t->getDatum() == x) return true;
return treeSearch(x, t->getLeft()) || treeSearch(x, t->getRight());
}
bool treeSearch(V x){
treeSearch(x, root);
}
// binary search value x in tree rooted at node t
bool treeBSearch(V x, TreeNode<V>* t){
if(t == nullptr) return false;
if(t->getDatum() == x) return true;
if(t->getDatum() > x)
return treeBSearch(x, t->getLeft());
else
return treeBSearch(x, t->getRight());
}
bool treeBSearch(V x){
return treeBSearch(x, root);
}
// check node t is leaf
bool isLeaf(TreeNode<V>* t){
if(t != nullptr && t->getLeft() == nullptr && t->getRight() == nullptr){
return true;
}else{
return false;
}
}
// find the height of the tree rooted at node t
int height(TreeNode<V>* t){
if(t == nullptr) return -1;
if(isLeaf(t)) return 0;
return 1 + std :: max(height(t->getLeft()), height(t->getRight()));
}
int height(){
return height(root);
}
// find the number of nodes of tree rooted at t
int nNodes(TreeNode<V>* t){
if(t == nullptr) return 0;
return 1 + nNodes(t->getLeft()) + nNodes(t->getRight());
}
int nNodes(){
return nNodes(root);
}
// insert value x to the current tree object
void insert(V x){
if(root == nullptr){
*root = x;
}
else{
cur = root;
while(cur != nullptr){
if(cur->getDatum() < x){
if(cur->getLeft() == nullptr){
cur->setDatum(x);
cur->setLeft(cur);
}
else{
cur = cur->getLeft();
}
}
else{
if(cur->getRight() == nullptr){
cur->setLeft(cur);
}
else{
cur = cur->getRight();
}
}
}
}
}
// print out the values of tree rooted at x
// it shows the hierarchy of the tree
// it will be useful for debugging
void print(TreeNode<V> * x, int indent){
if(x == nullptr) return;
if (x->getRight() != nullptr) {
print(x->getRight(), indent+4);
}
if (indent != 0) {
cout << std::setw(indent) << ' ';
}
if(x->getRight() != nullptr){
cout << " /\n" << std::setw(indent) << ' ';
}
cout << x->getDatum() << endl;
if (x->getLeft() != nullptr) {
cout << std::setw(indent) << ' ' <<" \\\n";
print(x->getLeft(), indent+4);
}
}
void print(){
int count = 0;
print(root, count);
}
};
This is the treeNode.h file that has the constructors for each function.
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
template <class T>
class TreeNode{
T datum;
TreeNode<T>* left, * right;
public:
// constructor with datum value, left and right are nullptr
TreeNode(T x){
datum = x;
left = nullptr;
right = nullptr;
}
// constructor with datum value, left and right values
TreeNode(T x, TreeNode<T>* lft, TreeNode<T>* rgt){
datum = x;
left = lft;
right = rgt;
}
//destructor releases left and right nodes, if not nullptr
~TreeNode(){
if (left) {
delete left;
}
if (right) {
delete right;
}
}
// get datum value
T getDatum(){
return datum;
}
// get left pointer
TreeNode<T>* getLeft(){
return left;
}
// get right pointer
TreeNode<T>* getRight(){
return right;
}
// set the left pointer
void setLeft(TreeNode<T>* p){
left = p;
}
// set the right pointer
void setRight(TreeNode<T>* p){
right = p;
}
};
This should be obvious:
if(root == nullptr){
*root = x;
^ ~~~~ dereferencing null pointer causes UB
}
root is nullptr, but you dereferece it and assign - this is UB undefined behaviour, this particular one causes segment faults.
How to solve it - you should instead of dereferencing null pointer, assign value to it:
if(root == nullptr){
root = new TreeNode<V>( x );
}