构建树视图算法

时间:2016-12-27 14:56:39

标签: arrays algorithm tree

我正在寻找从记录创建树视图的最佳和最快的算法。

我的数据库中有这样的记录:

ID|name|parent
1  Foo  0
2  Boo  1
3  Coo  1
4  Goo  2

我想收到的结构是:

[{
   name: Foo
   children: [
       {
          name: Boo
          children: [
               {
                   name: Goo
                   children: []
               }
          ]
       },
       {
           name: Coo
           children: []
       }
   ]
}]

我已尝试递归执行此操作,但我担心我的解决方案不够理想。

此致

1 个答案:

答案 0 :(得分:0)

通常,您分三步执行此操作:

首先,按 parent 对列表进行排序。

接下来,创建这些结构的哈希映射(字典),由ID索引。那就是:

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using Com.XMG.UnityLib.Community.OnlineGame;
using Com.XMG.UnityLib.Tracking;

#if UNITY_IPHONE
    using FlurryAccess = FlurryBinding;
#elif UNITY_ANDROID
    using FlurryAccess = FlurryAndroid;
#endif

public abstract partial class XMGAnalyticsManager : XMGSingleton<XMGAnalyticsManager> {

    #region Private Fields

    /// <summary>
    /// The type of the current build.
    /// For Analytics QA testing point to Analytics
    /// For QA Testing point to SandBox
    /// For Production/Release build point to Production
    /// </summary>
    [SerializeField]
    private BuildTypeEnum buildType = BuildTypeEnum.SandBox;

    /// <summary>
    /// The type of the android store type
    /// </summary>
    [SerializeField]
    private AndroidStoreTypeEnum androidStoreType = AndroidStoreTypeEnum.Google;

    /// <summary>
    /// The swrve authentication Info
    /// </summary>
    [SerializeField]
    private SwrveAuthentication swrveAuth;

    /// <summary>
    /// The flurry auth.
    /// </summary>
    [SerializeField]
    private FlurryAuthentication flurryAuth;

    /// <summary>
    ///  Sends events every X seconds.
    /// </summary>
    [SerializeField]
    private float sendEventsEverySeconds = 30;

    /// <summary>
    /// The parent screen.
    /// </summary>
    [SerializeField]
    private string parentScreen = "SplashPage";

    /// <summary>
    /// The grand parent screen.
    /// </summary>
    string grandParentScreen = "null";

    /// <summary>
    /// My external IP.
    /// </summary>
    string myExtIP = "";

    /// <summary>
    /// The swrve component.
    /// </summary>
    private SwrveComponent swrveComponent;

    private Dictionary<string, JSONObject> swrveResources = new Dictionary<string, JSONObject>();
    /// <summary>
    /// Resource hashmap ----> (key: resource name, value: json object)
    /// </summary>
    public Dictionary<string, JSONObject> Resources {
        get {
            return this.swrveResources;
        }
    }

    /// <summary>
    /// This event is triggered when swrve data is updated.
    /// </summary>
    public static event Action ItemsConfigurationUpdatedEvent = delegate {};

    /// <summary>
    /// True if we are in the fetching of AB Test Data State
    /// </summary>
    private bool isFetchingABParams = false;

    /// <summary>
    /// The current swrve identifier.
    /// </summary>
    private string currentSwrveId = null;

    /// <summary>
    /// The android bundle version.
    /// </summary>
    [SerializeField]
    protected string androidBundleVersion = "1.0";

    #endregion

    #region Public Properties

    /// <summary>
    /// True if it's a production build
    /// </summary>
    public bool IsProductionBuild {
        get { return buildType == BuildTypeEnum.Production; }
    }

    /// <summary>
    /// Gets the type of the android store.
    /// </summary>
    public AndroidStoreTypeEnum AndroidStoreType {
        get { return androidStoreType; }
        set { androidStoreType = value; }
    }

    /// <summary>
    /// True if its a google play store build
    /// </summary>
    public bool IsGoogleStoreType {
        get { return androidStoreType == AndroidStoreTypeEnum.Google; }
    }

    /// <summary>
    /// True if its an Amazon store build
    /// </summary>
    public bool IsAmazonStoreType {
        get { return androidStoreType == AndroidStoreTypeEnum.Amazon; }
    }

    #endregion

    #region Monobehaviors & Init

    /// <summary>
    /// Add event Listeners
    /// </summary>
    void Awake() {
        XMGOnlineProfileManager.OnProfileLoadedEvent += ProfileHasLoadedCallback;
        XMGCommunityManager.OnCreateRealProfileOnServerEvent += ProfileCreatedOnServerCallback;
        XMGCommunityManager.OnNewFriendsAddedEvent += NewFriendsAddedCallback;

        RegisterGameListeners();
        // TODO: Listen for Profile Change events
        swrveAuth.Init();
        flurryAuth.Init();
    }

    /// <summary>
    /// Starts the SWRVE Event timer
    /// </summary>
    void Start() {
        StartSwrveTimer();
        StartCoroutine( CheckIP() );
    }

    /// <summary>
    /// Checks for the IP
    /// </summary>
    /// <returns>
    /// The IP
    /// </returns>
    IEnumerator CheckIP(){

        WWW myExtIPWWW = new WWW("http://checkip.dyndns.org");

        if( myExtIPWWW != null ) {
            yield return myExtIPWWW;

            if( myExtIPWWW.error == null ) {

                myExtIP=myExtIPWWW.text;

                myExtIP=myExtIP.Substring(myExtIP.IndexOf(":")+1);

                myExtIP=myExtIP.Substring(0,myExtIP.IndexOf("<"));
            }
        }
    }


    /// <summary>
    /// Detach event Listeners
    /// </summary>
    void OnDestroy() {
        XMGOnlineProfileManager.OnProfileLoadedEvent -= ProfileHasLoadedCallback;
        XMGCommunityManager.OnCreateRealProfileOnServerEvent -= ProfileCreatedOnServerCallback;
        XMGCommunityManager.OnNewFriendsAddedEvent -= NewFriendsAddedCallback;
        UnRegisterGameListeners();
    }

    /// <summary>
    /// If Pause end session
    /// If Resume Start session and Re-Login.
    /// At any case post events
    /// </summary>
    /// <param name='pause'>
    /// Pause.
    /// </param>
    void OnApplicationPause(bool pause) {
        if (currentSwrveId != null) {

            if(pause) {
                swrveComponent.Swrve_AddSessionEnd();
            } else {
                swrveComponent.Swrve_AddSessionStart();
                SessionStart();
                AnalyticsLogout();
                AnalyticsLogin();
            }

            LogUserProperties();
            swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
        }
    }

    /// <summary>
    /// Init plugins.The Profile Has loaded
    /// </summary>
    void ProfileHasLoadedCallback() {
        // Get Swrve component
        swrveComponent = gameObject.GetComponentInChildren<SwrveComponent>();
        AnalyticsLogin();

        // Check if we've set the random user property, if not then set it
        if (!PlayerPrefs.HasKey(RANDOM_TEST_PROPERTY_KEY)) {
            PlayerPrefs.SetString(RANDOM_TEST_PROPERTY_KEY, "true");

            JSONObject userProperty = new JSONObject(JSONObject.Type.OBJECT);
            int random = NGUITools.RandomRange(1, 100);
            userProperty.AddField("RandNum", random.ToString());

            swrveComponent.Swrve_AddUserUpdateEvent(userProperty.ToString());
        }

    }

    /// <summary>
    /// A new Online Profile created callback.
    /// </summary>
    void ProfileCreatedOnServerCallback() {
        AddNamedEvent( "Community.FacebookUserCreated" );
    }

    /// <summary>
    /// New friends were added callback.
    /// </summary>
    /// <param name='newFriendsAdded'>
    /// The New friends added
    /// </param>
    void NewFriendsAddedCallback( int newFriendsAdded ) {
        JSONObject jTournamentFriends = new JSONObject(JSONObject.Type.OBJECT);
        jTournamentFriends.AddField( "NewFriendsAdded", BucketGeneral( newFriendsAdded ) );
        AddNamedEvent ( "Community.TournamentAddedFriend", jTournamentFriends );
    }

    #endregion

    #region Abstract & Virtual Methods

    /// <summary>
    /// Gets the tracking Object
    /// </summary>
    protected abstract XMGTracking GetTracking();

    /// <summary>
    /// Registers the game listeners.
    /// </summary>
    protected abstract void RegisterGameListeners();

    /// <summary>
    /// Unregister the game listeners.
    /// </summary>
    protected abstract void UnRegisterGameListeners();

    /// <summary>
    /// Logs the game user properties.
    /// </summary>
    /// <param name='jUserProperties'>
    /// The JSON Object with the user properties.
    /// </param>
    protected abstract void LogGameUserProperties(JSONObject jUserProperties);

    /// <summary>
    /// A session Is Started
    /// </summary>
    protected abstract void SessionStart();

    /// <summary>
    /// The session has ended
    /// </summary>
    protected abstract void SessionEnd();

    /// <summary>
    /// Gets the user start time.
    /// Default to Now
    /// Override to provide the user's start time
    /// </summary>
    /// <returns>
    /// The user start time.
    /// </returns>
    protected virtual long GetUserStartTime() {
        return (long)XMGUnityUtil.ToUnixTimeFromUniversal( DateTime.UtcNow );
    }

    #endregion

    #region Private Methods

    /// <summary>
    /// Starts the swrve timer.
    /// </summary>
    private void StartSwrveTimer() {
        StartCoroutine( StartEventsTimer());
    }

    /// <summary>
    /// Logs the user properties.
    /// And Calls abstract LogGameUserProperties
    /// </summary>
    private  void LogUserProperties() {
        JSONObject jUserProperties = new JSONObject(JSONObject.Type.OBJECT);
        // Add here Common User Properties
        XMGTracking tracking = GetTracking();

        jUserProperties.AddField( UP_FIRST_LAUNCH_DAY, tracking.FirstLaunch.Day );
        jUserProperties.AddField( UP_FIRST_LAUNCH_MONTH, tracking.FirstLaunch.Month );
        jUserProperties.AddField( UP_FIRST_LAUNCH_YEAR, tracking.FirstLaunch.Year );
        jUserProperties.AddField( UP_FIRST_LAUNCH_EPOCH, XMGUnityUtil.ToUnixTimeFromUniversal( tracking.FirstLaunch ).ToString() );
        jUserProperties.AddField( UP_TOTAL_TAPJOY_REWARDS, tracking.TotalTapJoyPointsEarned );
        jUserProperties.AddField( UP_TOTAL_SPONSORPAY_REWARDS, tracking.TotalSponsorPayPointsEarned );
        jUserProperties.AddField( UP_TOTAL_CHARTBOOST_ADS_CLICKED, tracking.TotalChartBoostAdsClicked );
        jUserProperties.AddField( UP_TOTAL_CHARTBOOST_ADS_CLOSED, tracking.TotalChartBoostAdsClosed );

        jUserProperties.AddField( UP_TOTAL_SOFT_CURRENCY_EARNED, tracking.SoftCurrencyEarned );
        jUserProperties.AddField( UP_TOTAL_HARD_CURRENCY_EARNED, tracking.HardCurrencyEarned );
        jUserProperties.AddField( UP_TOTAL_SOFT_CURRENCY_SPENT, tracking.SoftCurrencySpent );
        jUserProperties.AddField( UP_TOTAL_HARD_CURRENCY_SPENT, tracking.HardCurrencySpent );
#if UNITY_IPHONE
        jUserProperties.AddField( UP_VERION, EtceteraTwoBinding.getInfoPlistValue("CFBundleVersion") );
#elif UNITY_ANDROID
        jUserProperties.AddField( UP_VERION, androidBundleVersion );
#endif
        jUserProperties.AddField( UP_TOTAL_FRIENDS, tracking.TotalFriends );
        jUserProperties.AddField( UP_TOTAL_UNIQUE_DAYS_PLAYED, tracking.TotalUniqueDaysPlayed );
        if( string.IsNullOrEmpty( myExtIP ) ) {
            jUserProperties.AddField( UP_IP, myExtIP );
        }
        LogGameUserProperties(jUserProperties);
        swrveComponent.Swrve_AddUserUpdateEvent(jUserProperties.ToString());
    }

    #endregion

    #region Authentication

    /// <summary>
    /// Logs in to Flurry and SWRVE
    /// </summary>
    void AnalyticsLogin() {

        FlurryLogin();

        if( currentSwrveId != null ) {
            if( !currentSwrveId.Equals( XMGOnlineProfileManager.Instance.ActiveOnlineProfile.GameStats.SwrveId ) ) {
                Debug.Log("There is already a swrve account signed in!!! You must close the session first!");
                AnalyticsLogout();
            } else {
                // This swrve user is already logged in, bail out of this function
                Debug.Log("Attempt to log into swrve twice with the same id... Ignoring.");
                return;
            }
        }

        SwrveLogin(XMGOnlineProfileManager.Instance.ActiveOnlineProfile.GameStats.SwrveId);
    }

    /// <summary>
    /// Logout from Flurry and SWRVE
    /// </summary>
    private void AnalyticsLogout() {

        FlurryLogout();

        if(currentSwrveId == null) {
            Debug.LogError("There is no active swrve user!!! Cannot close session.");
            return;
        }

        SwrveLogout();
    }


    /// <summary>
    /// Starts Flurry Session
    /// </summary>
    private void FlurryLogin() {
#if UNITY_IPHONE
        FlurryAccess.startSession(flurryAuth.FlurryKey);
#elif UNITY_ANDROID
        FlurryAccess.onStartSession(flurryAuth.FlurryKey, true, true );
#endif
    }

    /// <summary>
    /// Logouts from Flurry
    /// </summary>
    private void FlurryLogout() {
#if UNITY_ANDROID
        FlurryAccess.onEndSession();
#endif
    }

    /// <summary>
    /// Login to Swrve
    /// </summary>
    /// <param name='swrveId'>
    /// The Swrve identifier.
    /// </param>
    private void SwrveLogin(string swrveId) {

        currentSwrveId = swrveId;

        int gameId = 0;
        string apiKey = "";

        if (buildType == BuildTypeEnum.Analytics) {
            gameId = swrveAuth.AnalyticsGameID;
            apiKey = swrveAuth.AnalyticsAPIKey;
        } else if (buildType == BuildTypeEnum.SandBox) {
            gameId = swrveAuth.SandboxGameID;
            apiKey = swrveAuth.SandboxAPIKey;
        } else if( buildType == BuildTypeEnum.Production ) {
            gameId = swrveAuth.ProductionGameID;
            apiKey = swrveAuth.ProductionAPIKey;
        }


        InitializeSWRVEComponent(gameId,
                    apiKey,
                    swrveAuth.PersonalKey,
                    swrveAuth.URLABTest
                    );
    }

    /// <summary>
    /// Logout from Swrve
    /// </summary>
    private void SwrveLogout() {
        // Make sure to close the Swrve session
        SessionEnd();
        swrveComponent.Swrve_AddSessionEnd();
        swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
        currentSwrveId = null;
    }

    /// <summary>
    /// Initializes the SWRVE component.
    /// </summary>
    /// <param name='gameId'>
    /// The Game identifier.
    /// </param>
    /// <param name='apiKey'>
    /// The API key.
    /// </param>
    /// <param name='personalKey'>
    /// Personal key.
    /// </param>
    /// <param name='abURL'>
    /// The Ab Test server URL.
    /// </param>
    private void InitializeSWRVEComponent(int gameId, string apiKey, string personalKey, string abURL) {
        swrveComponent.gameId = gameId;
        swrveComponent.apiKey = apiKey;
        swrveComponent.abTestServer = abURL;

        // Setup swrve for new user
        swrveComponent.Swrve_AddSessionStart();
        SessionStart();
        if(isFetchingABParams == false) {
            isFetchingABParams = true;
            StartCoroutine(GetAllResourcesWithABTests());
        }
    }

    #endregion

    #region A/B Helpers

    /// <summary>
    /// Returns the most recent JSON configuration for a resource from swrve or NULL if there is none.
    /// This means it could return cached data that is old. If you want to retireve the latest from swrve.
    /// </summary>
    /// <param name="itemId">
    /// A <see cref="System.String"/>
    /// </param>
    /// <returns>
    /// A <see cref="JSONObject"/>
    /// </returns>
    public JSONObject GetItemParams(string itemId) {
        JSONObject item = null;
        swrveResources.TryGetValue(itemId, out item);
        return item;
    }

    /// <summary>
    /// Gets all resources with AB tests.
    /// </summary>
    /// <returns>
    /// The all resources with AB tests.
    /// </returns>
    private IEnumerator GetAllResourcesWithABTests() {

        string swrveURLRequest = string.Format(swrveAuth.URLABTestResources + "?api_key={0}&user={1}&joined={2}", swrveComponent.apiKey, currentSwrveId, GetUserStartTime() );
        WWW itemRequest = new WWW(swrveURLRequest);
        yield return itemRequest; // Yield until a result is returned.

        if(itemRequest.error != null) {
            Debug.LogWarning("Error attempting to fetch Swrve A/B Resource data: " + itemRequest.error);
            LoadCachedConfig();
            // Bail!
            yield break;
        }

        // Process all the resources into a hashmap ( key: resource name,  value: json object )
        JSONObject jResources = new JSONObject(itemRequest.text);
        if(jResources == null || jResources.type == JSONObject.Type.NULL) {
            // Bad data from swrve, abort!
            Debug.LogError("Bad A/B resource data from swrve!");
            LoadCachedConfig();
            yield break;
        } else {
            //Debug.LogWarning( "Data from SWRVE: " + jResources );
        }

        XMGSaveLoadUtils.Instance.SaveEncryptedField(CACHED_CONFIG_KEY, itemRequest.text); // Replace the old cached config.
        ParseResources(jResources);
        if( swrveResources.Count > 0 ) {
            ItemsConfigurationUpdatedEvent();
        }

        isFetchingABParams = false;
    }

    /// <summary>
    /// Loads the cached swrve config file
    /// </summary>
    private void LoadCachedConfig() {
        string cached = XMGSaveLoadUtils.Instance.LoadEncryptedField(CACHED_CONFIG_KEY);

        if(string.IsNullOrEmpty(cached)) {
            // There was no cached data
            return;
        }

        // Apply the cached resources so it doesn't block updates.
        JSONObject jResources = new JSONObject(cached);
        ParseResources(jResources);
        if( swrveResources.Count > 0 ) {
            ItemsConfigurationUpdatedEvent();
        }
    }

    /// <summary>
    ///  Expects a JSON array of resources.
    /// </summary>
    /// <param name="resources">
    /// A <see cref="JSONObject"/>
    /// </param>
    private void ParseResources(JSONObject jResources) {
        swrveResources.Clear();
        //Debug.Log( "Parsing SWRVE Resources: " + jResources );

        if(jResources == null || jResources.type != JSONObject.Type.ARRAY) {
            // Bad data from swrve, abort!
            Debug.LogError("Could not parse resource data, unexpected format!");
            return;
        }

        foreach(JSONObject data in jResources.list) {
            JSONObject key = data.GetField("uid");
            if(key == null || key.type != JSONObject.Type.STRING || string.IsNullOrEmpty( JSONObject.GetString(key) ) ) {
                // Bad item, on to the next
                Debug.LogWarning("Bad item, no property 'uid' in " + data.print());
                continue;
            }
            // Add the resource over top of any precached configuration
            swrveResources[JSONObject.GetString(key)] = data;
        }
    }

    #endregion

    #region Event Handling

    /// <summary>
    /// Adds the named event.
    /// </summary>
    /// <param name='name'>
    /// The EventName.
    /// </param>
    public void AddNamedEvent(string name) {
        swrveComponent.Swrve_AddNamedEvent(name, "{}");
#if UNITY_IPHONE
        FlurryAccess.logEvent(name, false);
#elif UNITY_ANDROID
        FlurryAccess.logEvent(name);
#endif
    }

    /// <summary>
    /// Adds a named event with Payload
    /// </summary>
    /// <param name='name'>
    /// The EventName.
    /// </param>
    /// <param name='payload'>
    /// Payload.
    /// </param>
    public void AddNamedEvent(string name, JSONObject payload) {
        string data = payload.ToString();

        if(!string.IsNullOrEmpty(data)) {
            swrveComponent.Swrve_AddNamedEvent(name, data);
        } else {
            swrveComponent.Swrve_AddNamedEvent(name, "{}");
        }

#if UNITY_IPHONE
        FlurryAccess.logEventWithParameters(name, payload.ToDictionary(), false);
#elif UNITY_ANDROID
        FlurryAccess.logEvent(name, payload.ToDictionary());
#endif
    }

    /// <summary>
    /// Infinite Loop to send events in the queue
    /// </summary>
    private IEnumerator StartEventsTimer() {
        float timeInterval = Time.realtimeSinceStartup;
        while (true) {
            if(Time.realtimeSinceStartup - timeInterval >= sendEventsEverySeconds) {
                SendEvents();
                timeInterval = Time.realtimeSinceStartup;
            }
            yield return new WaitForSeconds( 1 );
        }
    }

    /// <summary>
    /// Sends the events to SWRVE
    /// </summary>
    private void SendEvents() {
        if(currentSwrveId != null) {
            swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
        }
    }

    #endregion

    #region UI Events

    /// <summary>
    /// Logs a UI screen visit event.
    /// Records the current screen, the parent screen, and the grandparent screen.
    /// </summary>
    /// <param name='screenName'>
    /// Screen name.
    /// </param>
    public void ScreenVisitEvent(string screenName) {

        JSONObject eventParams = new JSONObject(JSONObject.Type.OBJECT);

        if(parentScreen != null) {
            eventParams.AddField("parent", parentScreen);
        }
        if(grandParentScreen != null) {
            eventParams.AddField("grandParent", grandParentScreen);
        }

        AddNamedEvent("UI.Flow." + screenName, eventParams);

        eventParams.AddField("screen", screenName);
        AddNamedEvent("UI.Flow.Screens", eventParams);

        grandParentScreen = parentScreen;
        parentScreen = screenName;
    }

    /// <summary>
    /// Logs a UI button pressed event
    /// </summary>
    /// <param name='buttonName'>
    /// The Button name.
    /// </param>
    public void ButtonPressedEvent( string buttonName ) {
        AddNamedEvent("UI.ButtonPressed." + buttonName );
    }

    #endregion

    #region Purchase Events

    /// <summary>
    /// Sends an analytics buy in event for purchasing inapps.
    /// </summary>
    /// <param name='rewardCurrency'>
    /// Reward currency
    /// </param>
    /// <param name='rewardAmount'>
    ///  Amount of rewardCurrency
    /// </param>
    /// <param name='localCost'>
    /// The real money price in local currency ( e.g 0.99 )
    /// </param>
    /// <param name='localCurrency'>
    /// CAD, USD, etc...
    /// </param>
    public void AddBuyInEvent(string rewardCurrency, int rewardAmount, float localCost, string localCurrency, string itemID ) {
        swrveComponent.Swrve_AddBuyInEvent( "", rewardCurrency, rewardAmount, Mathf.Round(localCost* 100)/100, localCurrency );
        AddNamedEvent( "Purhases.IAP" );
        AddNamedEvent( "Purhases.IAP.HardCurrency" + itemID );
    }

    /// <summary>
    /// Analytics event when a user purchases an item in-game.
    /// </summary>
    /// <param name='itemID'>
    /// The UID for the Item
    /// </param>
    /// <param name='currency'>
    /// The currency
    /// </param>
    /// <param name='itemCost'>
    /// Item cost.
    /// </param>
    /// <param name='itemQuantity'>
    /// Item quantity.
    /// </param>
    public void AddPurchaseEvent( string itemID, string currency, int itemCost, int itemQuantity ) {
        swrveComponent.Swrve_AddPurchaseItemEvent( itemID, currency, itemCost, itemQuantity );
    }

    /// <summary>
    /// Adds a purchase conversion event.
    /// </summary>
    /// <param name='itemID'>
    ///The unique Item ID
    /// </param>
    public void AddPurchaseConversionPackEvent( string itemID ) {
        AddNamedEvent( "Purchases.HardCurrency.SoftCurrency" );
        AddNamedEvent( "Purhases.HardCurrency.SoftCurrency." + itemID );
    }

    #endregion
}

(我只是使用类似C的伪代码。您必须翻译成您正在使用的任何语言。)

然后,按顺序浏览列表。对于每个节点,将其添加到字典中,并将其添加到父节点。那就是:

class Node
{
    int Id;
    string Name;
    int Parent;
    List<node> Children;
}

此时您有一个字典,其中包含顶级的所有项目。但是有孩子的节点也会填充它们。然后,您可以通过遍历字典输出树视图,并仅输出父项为0的节点。即:

for each item in list
    newNode = CreateNewNode(id, name, parent);
    dictionary.Add(id, newNode);
    if (parent != 0)
        dictionary[parent].Children.Add(newNode);

// Output initial stuff here // and then output all top-level nodes for each node in dictionary if (node.Parent == 0) { outputNode(node, 0); } // Output closing braces here 是递归的:

outputNode